UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

298 lines (208 loc) • 7.98 kB
import { uint8_to_float } from "../../../src/core/binary/uint8_to_float.js"; import { QuadTreeNode } from "../../../src/core/geom/2d/quad-tree/QuadTreeNode.js"; import { clamp } from "../../../src/core/math/clamp.js"; import { inverseLerp } from "../../../src/core/math/inverseLerp.js"; import { lerp } from "../../../src/core/math/lerp.js"; import { Transform } from "../../../src/engine/ecs/transform/Transform.js"; import { PatchTerrainHeightAction } from "../../actions/concrete/PatchTerrainHeightAction.js"; import TransformModifyAction from "../../actions/concrete/TransformModifyAction.js"; import EditorEntity from "../../ecs/EditorEntity.js"; import { TerrainPaintTool } from "./TerrainPaintTool.js"; const LIMIT_VALUE = 1000; /** * * @param {Terrain} terrain * @param {number} uv_x0 * @param {number} uv_y0 * @param {number} uv_x1 * @param {number} uv_y1 * @param {Sampler2D} marker * @param {number} value_delta * @param {number} limitMin * @param {number} limitMax * @returns {PatchTerrainHeightAction} */ function make_mod_action(terrain, uv_x0, uv_y0, uv_x1, uv_y1, marker, value_delta, limitMin, limitMax) { const heightMap = terrain.samplerHeight; const h_x0 = uv_x0 * heightMap.width; const h_x1 = uv_x1 * heightMap.width; const h_y0 = uv_y0 * heightMap.height; const h_y1 = uv_y1 * heightMap.height; const x0 = Math.ceil(h_x0); const x1 = Math.floor(h_x1); const y0 = Math.ceil(h_y0); const y1 = Math.floor(h_y1); const action = new PatchTerrainHeightAction(terrain, x0, y0, (x1 - x0), (y1 - y0)); const patch_destination_data = action.patch.data; for (let y = y0; y < y1; y++) { const v = inverseLerp(h_y0, h_y1 - 1, y); for (let x = x0; x < x1; x++) { const u = inverseLerp(h_x0, h_x1 - 1, x); //Get alpha const markerValue = marker.sampleChannelBilinearUV(u, v, 3); const marker_value_normalized = uint8_to_float(markerValue); const source_address = y * heightMap.width + x; const base = heightMap.data[source_address]; const patchAddress = (y - y0) * action.patch.width + (x - x0); const targetValue = clamp(base + value_delta, limitMin, limitMax); const value = lerp(base, targetValue, marker_value_normalized); if (Number.isNaN(value)) { // console.warn('.'); patch_destination_data[patchAddress] = base; } else { patch_destination_data[patchAddress] = value; } } } return action; } export class TerrainHeightPaintTool extends TerrainPaintTool { constructor() { super(); this.settings.limitMin = -LIMIT_VALUE; this.settings.limitMax = LIMIT_VALUE; /** * * @type {QuadTreeNode<number>} */ this.transform_index = new QuadTreeNode(); } buildTransformIndex() { /** * * @type {Engine} */ const engine = this.engine; /** * * @type {EntityManager} */ const entityManager = engine.entityManager; /** * * @type {EntityComponentDataset} */ const dataset = entityManager.dataset; //purge existing data this.transform_index.clear(); /** * * @type {number} */ const gridScale = this.terrain.gridScale; const uS = 1 / (this.terrain.size.x * gridScale); const vS = 1 / (this.terrain.size.y * gridScale); dataset.traverseComponents(Transform, (transform, entity) => { if (dataset.getComponent(entity, EditorEntity) !== undefined) { //it's an editor entity return; } const position = transform.position; const x = position.x; const z = position.z; const u = x * uS; const v = z * vS; const datum = this.transform_index.add(transform, u, v, u, v); datum.entity = entity; }); } initialize() { super.initialize(); this.buildTransformIndex(); } finalize() { super.finalize(); } /** * * @param {number} timeDelta */ async paint(timeDelta) { const power = this.settings.brushStrength * timeDelta; /** * * @type {Terrain} */ const terrain = this.terrain; const brushPosition = this.__brushPosition; const brushSize = this.settings.brushSize; const terrainSize = terrain.size; const uv_x = brushPosition.x / terrainSize.x; const uv_y = brushPosition.y / terrainSize.y; const uv_w = brushSize / terrainSize.x; const uv_h = brushSize / terrainSize.y; const uv_x0 = uv_x - uv_w / 2; const uv_x1 = uv_x + uv_w / 2; const uv_y0 = uv_y - uv_h / 2; const uv_y1 = uv_y + uv_h / 2; const marker = this.settings.marker; const direction = this.modifiers.shift ? -1 : 1; const value_delta = power * direction; const limitMin = this.settings.limitMin; const limitMax = this.settings.limitMax; const action = make_mod_action(terrain, uv_x0, uv_y0, uv_x1, uv_y1, marker, value_delta, limitMin, limitMax); this.editor.actions.do(action); const objectMoveActions = this.createObjectMoveActions(action); this.editor.actions.doMany(objectMoveActions); } /** * * @param {PatchTerrainHeightAction} action * @returns {Action[]} */ createObjectMoveActions(action) { const heightSampler = this.terrain.samplerHeight; const x0 = action.x; const y0 = action.y; const patch = action.patch; const patch_width = patch.width; const patch_height = patch.height; const x1 = x0 + patch_width; const y1 = y0 + patch_height; const u0 = x0 / heightSampler.width; const v0 = y0 / heightSampler.height; const u1 = x1 / heightSampler.width; const v1 = y1 / heightSampler.height; /** * * @type {QuadTreeDatum<Transform>[]} */ const leaves = []; /** * * @type {Action[]} */ const result = []; const overlaps = this.transform_index.requestDatumIntersectionsRectangle(leaves, u0, v0, u1, v1); for (let i = 0; i < overlaps; i++) { const leaf = leaves[i]; /** * * @type {Transform} */ const transform = leaf.data; const tX = leaf.x0 * heightSampler.width; const tY = leaf.y0 * heightSampler.height; const action_value = action.patch.sampleChannelBicubic(tX - action.x, tY - action.y, 0); const existing_value = heightSampler.sampleChannelBicubic(tY, tY, 0); const delta = action_value - existing_value; if (delta === 0) { continue; } if (Number.isNaN(delta)) { console.error('Delta is NaN, skipping.', action, tX, tY); continue; } const tM = new Transform(); tM.copy(transform); tM.position._add(0, delta, 0); const a = new TransformModifyAction(leaf.entity, tM); result.push(a); } return result; } start() { super.start(); this.editor.actions.mark('terrain height paint'); } }