@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
350 lines (267 loc) • 9.87 kB
JavaScript
import { BinaryDataType } from "../../src/core/binary/type/BinaryDataType.js";
import {
compute_typed_array_constructor_from_data_type
} from "../../src/core/binary/type/DataType2TypedArrayConstructorMapping.js";
import { Cache } from "../../src/core/cache/Cache.js";
import Signal from "../../src/core/events/signal/Signal.js";
import { noop } from "../../src/core/function/noop.js";
import { strictEquals } from "../../src/core/function/strictEquals.js";
import { computeStringHash } from "../../src/core/primitives/strings/computeStringHash.js";
import { ArrayBufferLoader } from "../../src/engine/asset/loaders/ArrayBufferLoader.js";
import { TextureAssetLoader } from "../../src/engine/asset/loaders/texture/TextureAssetLoader.js";
import { TerrainLayer } from "../../src/engine/ecs/terrain/ecs/layers/TerrainLayer.js";
import { TerrainFlags } from "../../src/engine/ecs/terrain/ecs/TerrainFlags.js";
import { obtainTerrain } from "../../src/engine/ecs/terrain/util/obtainTerrain.js";
import { EngineConfiguration } from "../../src/engine/EngineConfiguration.js";
import { EngineHarness } from "../../src/engine/EngineHarness.js";
import { load_and_set_cubemap_v0 } from "../../src/engine/graphics/load_and_set_cubemap_v0.js";
import CheckersTextureURI from "../../src/engine/graphics/texture/CheckersTextureURI.js";
import { sampler2d_scale } from "../../src/engine/graphics/texture/sampler/resize/sampler2d_scale.js";
import { Sampler2D } from "../../src/engine/graphics/texture/sampler/Sampler2D.js";
import sampler2d_to_html_canvas from "../../src/engine/graphics/texture/sampler/sampler2d_to_html_canvas.js";
import { sampler2d_transfer_data } from "../../src/engine/graphics/texture/sampler/sampler2d_transfer_data.js";
import { NativeListController } from "../../src/view/controller/controls/NativeListController.js";
import { CanvasView } from "../../src/view/elements/CanvasView.js";
import EmptyView from "../../src/view/elements/EmptyView.js";
const engineHarness = new EngineHarness();
/**
*
* @param {Engine} engine
*/
function makeConfig(engine) {
const config = new EngineConfiguration();
config.addLoader('arraybuffer', new ArrayBufferLoader());
config.addLoader('texture', new TextureAssetLoader());
return config;
}
class EditorTextureSlot {
constructor() {
this.onChanged = new Signal();
this.sampler = null;
this.__read = noop;
this.__write = noop;
/**
*
* @type {number}
*/
this.itemSize = 1;
}
read() {
this.__read(this.sampler);
this.onChanged.send0();
}
write() {
this.__write(this.sampler);
}
/**
*
* @param {(Sampler2D)=>any} read
* @param {(Sampler2D)=>any} write
* @param {number} [itemSize]
* @param {BinaryDataType} [dataType]
* @return {EditorTextureSlot}
*/
static from({ read, write, itemSize = 1, dataType = BinaryDataType.Uint8 }) {
const r = new EditorTextureSlot();
r.__read = read;
r.__write = write;
const TA = compute_typed_array_constructor_from_data_type(dataType);
r.sampler = new Sampler2D(new TA(itemSize), itemSize, 1, 1);
return r;
}
}
class TextureLayerEditor {
constructor() {
this.index = 0;
/**
*
* @type {Terrain|null}
* @private
*/
this.__terrain = null;
this.__splat = EditorTextureSlot.from({
read: (target) => {
const terrain = this.__terrain;
target.resize(terrain.splat.size.x, terrain.splat.size.y)
terrain.splat.readLayerToSampler(target, this.index, 0);
},
write: (source) => {
this.__terrain.splat.writeLayerFromSampler(source, this.index, 0);
},
itemSize: 1
});
this.__diffuse = EditorTextureSlot.from({
read: (target) => {
const layers = this.__terrain.layers;
const layer = layers.get(this.index);
target.resize(layer.diffuse.width, layer.diffuse.height);
sampler2d_transfer_data(layer.diffuse, target);
},
write: (source) => {
const layer = this.__terrain.layers.get(this.index);
sampler2d_transfer_data(source, layer.diffuse);
this.__terrain.layers.writeLayerDataIntoTexture(this.index);
},
itemSize: 3
});
}
/**
* @param {Terrain} t
*/
set terrain(t) {
this.__terrain = t;
}
init() {
this.__splat.read();
this.__diffuse.read();
}
}
/**
*
* @param {EditorTextureSlot} slot
*/
function makeTextureSlotView({ slot }) {
const preview = new CanvasView();
preview.size.set(64, 64);
function update() {
sampler2d_to_html_canvas(slot.sampler, 1, 0, preview.el);
}
preview.on.linked.add(update);
preview.bindSignal(slot.onChanged, update);
return preview;
}
/**
*
* @param {View} root
* @param {Terrain} terrain
* @param {Engine} engine
*/
function makeTerrainEditor(root, terrain, engine) {
/**
*
* @type {Cache<string, Sampler2D>}
*/
const PREVIEW_CACHE = new Cache({
keyHashFunction: computeStringHash,
keyEqualityFunction: strictEquals
});
const PREVIEW_SIZE = 64;
/**
*
* @param {string} url
* @return {Promise<Sampler2D>}
*/
async function getPreviewFromURL(url) {
if (PREVIEW_CACHE.contains(url)) {
return PREVIEW_CACHE.get(url);
}
const asset = await engine.assetManager.promise(url, 'image');
// check cache again, might have been loaded in the meanwhile
if (PREVIEW_CACHE.contains(url)) {
return PREVIEW_CACHE.get(url);
}
const source = asset.create();
const preview = Sampler2D.uint8(4, PREVIEW_SIZE, PREVIEW_SIZE);
sampler2d_scale(source, preview);
PREVIEW_CACHE.put(url, preview);
return preview;
}
/**
*
* @type {EditorTextureSlot|null}
*/
let active_slot = null;
function rebuild() {
// rebuild terrain
terrain.clearFlag(TerrainFlags.Built);
terrain.build(engine.assetManager);
}
function build_splat_editor() {
/**
*
* @type {Map<TerrainLayer, TextureLayerEditor>}
*/
const editors = new Map();
return new NativeListController({
model: terrain.layers.layers,
elementFactory() {
return TerrainLayer.from(CheckersTextureURI, 1, 1);
},
/**
*
* @param {TerrainLayer} layer
*/
elementViewFactory(layer) {
const e = new TextureLayerEditor();
editors.set(layer, e);
const layer_index = terrain.layers.layers.indexOf(layer);
e.terrain = terrain;
e.index = layer_index;
e.init();
const view = new EmptyView();
view.addChild(makeTextureSlotView({
slot: e.__diffuse
}));
view.addChild(makeTextureSlotView({
slot: e.__splat
}));
view.bindSignal(layer.onChanged, e.__diffuse.read, e.__diffuse);
return view;
},
operationAdd(list, el) {
terrain.layers.addLayer(el);
terrain.splat.addWeightLayer();
},
operationRemove(list, el) {
const index = terrain.layers.layers.indexOf(el);
terrain.layers.removeLayer(el);
terrain.splat.removeWeightLayer(index);
editors.delete(el);
// re-shuffle remaining editors
for (const [layer, editor] of editors) {
if (editor.index > index) {
editor.index--;
editor.init();
}
}
}
});
}
root.addChild(build_splat_editor());
}
/**
*
* @param {Engine} engine
* @return {string}
*/
async function main(engine) {
//disable drag and drop on game canvas
engine.graphics.domElement.addEventListener('dragover', (e) => e.preventDefault());
engine.graphics.domElement.addEventListener('drop', (e) => e.preventDefault());
EngineHarness.buildLights({ engine });
const camera = EngineHarness.buildCamera({ engine });
EngineHarness.buildOrbitalCameraController({ engine, cameraEntity: camera.id })
await EngineHarness.buildTerrain({ engine });
const vMain = new EmptyView({ classList: ['ui-texture-testing-tool-view'] });
vMain.css({
position: "absolute",
left: "0",
top: "0",
pointerEvents: "initial"
});
makeTerrainEditor(vMain, obtainTerrain(engine.entityManager.dataset), engine);
document.body.appendChild(vMain.el);
vMain.link();
}
/**
*
* @param {EngineHarness} harness
*/
async function init(harness) {
const engine = harness.engine;
await makeConfig(engine).apply(engine);
await load_and_set_cubemap_v0(engine.graphics, 'data/textures/cubemaps/hip_miramar/32/', '.png');
await harness.initialize();
main(engine);
}
init(engineHarness);