From 53c7bbc2ae55d705b4671c080f0512584470b284 Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Sat, 5 Nov 2022 22:00:31 +0000 Subject: [PATCH] Added light threshold option --- src/app_context.ts | 1 + src/block_mesh.ts | 104 ++++++++++++++++++++++++++++++-------- src/ui/elements/base.ts | 2 +- src/ui/elements/slider.ts | 4 +- src/ui/layout.ts | 3 +- src/worker_types.ts | 1 + tools/headless-config.ts | 1 + 7 files changed, 92 insertions(+), 24 deletions(-) diff --git a/src/app_context.ts b/src/app_context.ts index ff0ce29..c6548df 100644 --- a/src/app_context.ts +++ b/src/app_context.ts @@ -332,6 +332,7 @@ export class AppContext { colourSpace: ColourSpace.RGB, fallable: uiElements.fallable.getCachedValue() as FallableBehaviour, resolution: Math.pow(2, uiElements.colourAccuracy.getCachedValue()), + lightThreshold: uiElements.lightThreshold.getCachedValue(), }, }; diff --git a/src/block_mesh.ts b/src/block_mesh.ts index 8e1ddc1..25e2d94 100644 --- a/src/block_mesh.ts +++ b/src/block_mesh.ts @@ -21,6 +21,8 @@ interface Block { blockInfo: BlockInfo; } +type LightAction = { pos: Vector3, value: number }; + export type FallableBehaviour = 'replace-falling' | 'replace-fallable' | 'place-string' | 'do-nothing'; export interface BlockMeshParams { @@ -46,7 +48,7 @@ export class BlockMesh { public static createFromVoxelMesh(voxelMesh: VoxelMesh, blockMeshParams: AssignParams.Input) { const blockMesh = new BlockMesh(voxelMesh); blockMesh._assignBlocks(blockMeshParams); - blockMesh._calculateLighting(); + blockMesh._calculateLighting(blockMeshParams.lightThreshold); return blockMesh; } @@ -179,7 +181,16 @@ export class BlockMesh { } } - private _calculateLighting() { + private _setEmissiveBlock(pos: Vector3) { + const emissiveBlockName = 'minecraft:glowstone'; + const emissiveBlockData = this._atlas.getBlocks().get(emissiveBlockName); + ASSERT(emissiveBlockData !== undefined, 'No emissive block data found'); + const blockIndex = this._voxelMesh.getVoxelIndex(pos); + ASSERT(blockIndex !== undefined, 'Setting emissive block of block that doesn\'t exist'); + this._blocks[blockIndex].blockInfo = emissiveBlockData; + } + + private _calculateLighting(lightThreshold: number) { const blocksBounds = this._voxelMesh.getBounds(); const sizeVector = blocksBounds.getDimensions().add(1).add(2); @@ -207,8 +218,7 @@ export class BlockMesh { // TODO: Cache stringify - const actions: { pos: Vector3, value: number }[] = []; // = [{ pos: blocksBounds.min, value: 15 }]; - + const actions: LightAction[] = []; // = [{ pos: blocksBounds.min, value: 15 }]; // Add initial light emitters to top of mesh to simulate sunlight for (let x = blocksBounds.min.x - 1; x <= blocksBounds.max.x + 1; ++x) { for (let z = blocksBounds.min.z - 1; z <= blocksBounds.max.z + 1; ++z) { @@ -218,10 +228,50 @@ export class BlockMesh { }); } } + this._handleActionsList(actions); + ASSERT(actions.length === 0, 'Actions still remaining'); + if (lightThreshold > 0) { + const blocksNeedAttention: Vector3[] = []; + + this.getBlocks().forEach((block) => { + if (this._internalGetLight(block.voxel.position)! <= lightThreshold) { + // TODO: Add additional requirement that the block must have a visible face + // before being added to the list otherwise all blocks that are not visible + // will be turned into an emissive block + blocksNeedAttention.push(block.voxel.position); + } + }); + + while (blocksNeedAttention.length > 0) { + const blockPos = blocksNeedAttention.pop()!; + + const currentLightLevel = this._internalGetLight(blockPos)!; + const newLightLevel = 14; + + if (currentLightLevel < lightThreshold) { + this._internalSetLight(blockPos, newLightLevel); + this._setEmissiveBlock(blockPos); + + actions.push({ pos: new Vector3(0, 1, 0).add(blockPos), value: newLightLevel - 1 }); + actions.push({ pos: new Vector3(1, 0, 0).add(blockPos), value: newLightLevel - 1 }); + actions.push({ pos: new Vector3(0, 0, 1).add(blockPos), value: newLightLevel - 1 }); + actions.push({ pos: new Vector3(-1, 0, 0).add(blockPos), value: newLightLevel - 1 }); + actions.push({ pos: new Vector3(0, 0, -1).add(blockPos), value: newLightLevel - 1 }); + actions.push({ pos: new Vector3(0, -1, 0).add(blockPos), value: newLightLevel - 1 }); + // Assuming emissive block cannot be transparent! + } + + this._handleActionsList(actions); + ASSERT(actions.length === 0, 'Actions still remaining'); + } + } + } + + private _handleActionsList(actions: LightAction[]) { while (actions.length > 0) { const action = actions.pop()!; - const newLightValue = action.value; + let newLightValue = action.value; if (!this._posIsValid(action.pos)) { continue; @@ -230,31 +280,45 @@ export class BlockMesh { // Update lighting values only if the new value is lighter than the current brightness. const blockHere = this.getBlockAt(action.pos); - if (blockHere !== undefined && this._emissiveBlocks.includes(blockHere.blockInfo.name)) { - if (this._internalGetLight(action.pos)! < 14) { - this._internalSetLight(action.pos, 14); - actions.push({ pos: new Vector3(0, 1, 0).add(action.pos), value: 14 }); - actions.push({ pos: new Vector3(1, 0, 0).add(action.pos), value: 14 }); - actions.push({ pos: new Vector3(0, 0, 1).add(action.pos), value: 14 }); - actions.push({ pos: new Vector3(-1, 0, 0).add(action.pos), value: 14 }); - actions.push({ pos: new Vector3(0, 0, -1).add(action.pos), value: 14 }); - actions.push({ pos: new Vector3(0, -1, 0).add(action.pos), value: 14 }); - } - } else if (newLightValue > currentLightValue) { - if (blockHere === undefined || this._transparentBlocks.includes(blockHere.blockInfo.name)) { - this._internalSetLight(action.pos, newLightValue); + const isBlockHere = blockHere !== undefined; - actions.push({ pos: new Vector3(0, 1, 0).add(action.pos), value: newLightValue - 1 }); // up + if (isBlockHere) { + const isEmissiveBlock = this._emissiveBlocks.includes(blockHere.blockInfo.name); + const isTransparentBlock = this._transparentBlocks.includes(this.getBlockAt(action.pos)!.blockInfo.name); + + if (isEmissiveBlock) { + newLightValue = 14; + } + + if (newLightValue > currentLightValue) { + this._internalSetLight(action.pos, newLightValue); + if (isEmissiveBlock || isTransparentBlock) { + actions.push({ pos: new Vector3(0, 1, 0).add(action.pos), value: newLightValue - 1 }); + actions.push({ pos: new Vector3(1, 0, 0).add(action.pos), value: newLightValue - 1 }); + actions.push({ pos: new Vector3(0, 0, 1).add(action.pos), value: newLightValue - 1 }); + actions.push({ pos: new Vector3(-1, 0, 0).add(action.pos), value: newLightValue - 1 }); + actions.push({ pos: new Vector3(0, 0, -1).add(action.pos), value: newLightValue - 1 }); + actions.push({ pos: new Vector3(0, -1, 0).add(action.pos), value: isTransparentBlock ? newLightValue : newLightValue - 1 }); + } + } + } else { + if (newLightValue > currentLightValue) { + this._internalSetLight(action.pos, newLightValue); + actions.push({ pos: new Vector3(0, 1, 0).add(action.pos), value: newLightValue - 1 }); actions.push({ pos: new Vector3(1, 0, 0).add(action.pos), value: newLightValue - 1 }); actions.push({ pos: new Vector3(0, 0, 1).add(action.pos), value: newLightValue - 1 }); actions.push({ pos: new Vector3(-1, 0, 0).add(action.pos), value: newLightValue - 1 }); actions.push({ pos: new Vector3(0, 0, -1).add(action.pos), value: newLightValue - 1 }); - actions.push({ pos: new Vector3(0, -1, 0).add(action.pos), value: newLightValue === 15 ? 15 : newLightValue - 1 }); // down + actions.push({ pos: new Vector3(0, -1, 0).add(action.pos), value: newLightValue === 15 ? 15 : newLightValue - 1 }); } } } } + private _updateBlockLightLevel(pos: Vector3, value: number) { + + } + public getBlockAt(pos: Vector3): TOptional { const index = this._voxelMesh.getVoxelIndex(pos); if (index !== undefined) { diff --git a/src/ui/elements/base.ts b/src/ui/elements/base.ts index a0c1ff4..698a6f9 100644 --- a/src/ui/elements/base.ts +++ b/src/ui/elements/base.ts @@ -24,7 +24,7 @@ export abstract class BaseUIElement { } protected getValue(): Type { - ASSERT(this._value); + ASSERT(this._value !== undefined); return this._value; } diff --git a/src/ui/elements/slider.ts b/src/ui/elements/slider.ts index 7245861..598f6fb 100644 --- a/src/ui/elements/slider.ts +++ b/src/ui/elements/slider.ts @@ -90,7 +90,7 @@ export class SliderElement extends LabelledElement { if (!this._isEnabled) { return; } - ASSERT(this._value); + ASSERT(this._value !== undefined); this._value -= (e.deltaY / 150) * this._step; this._value = clamp(this._value, this._min, this._max); @@ -109,7 +109,7 @@ export class SliderElement extends LabelledElement { const box = element.getBoundingClientRect(); const left = box.x; const right = box.x + box.width; - + this._value = mapRange(e.clientX, left, right, this._min, this._max); this._value = clamp(this._value, this._min, this._max); diff --git a/src/ui/layout.ts b/src/ui/layout.ts index 857627f..23c3d47 100644 --- a/src/ui/layout.ts +++ b/src/ui/layout.ts @@ -151,8 +151,9 @@ export class UI { }, ]), 'colourAccuracy': new SliderElement('Colour accuracy', 1, 8, 1, 5, 0.1), + 'lightThreshold': new SliderElement('Light threshold', 0, 14, 0, 0, 1), }, - elementsOrder: ['textureAtlas', 'blockPalette', 'dithering', 'fallable', 'colourAccuracy'], + elementsOrder: ['textureAtlas', 'blockPalette', 'dithering', 'fallable', 'colourAccuracy', 'lightThreshold'], submitButton: new ButtonElement('Assign blocks', () => { this._appContext.do(EAction.Assign); }), diff --git a/src/worker_types.ts b/src/worker_types.ts index 48a7f16..2f83d3a 100644 --- a/src/worker_types.ts +++ b/src/worker_types.ts @@ -96,6 +96,7 @@ export namespace AssignParams { colourSpace: ColourSpace, fallable: FallableBehaviour, resolution: RGBAUtil.TColourAccuracy, + lightThreshold: number, } export type Output = { diff --git a/tools/headless-config.ts b/tools/headless-config.ts index d210ea2..740ef58 100644 --- a/tools/headless-config.ts +++ b/tools/headless-config.ts @@ -21,6 +21,7 @@ export const headlessConfig: THeadlessConfig = { colourSpace: ColourSpace.RGB, fallable: 'replace-falling', resolution: 32, + lightThreshold: 0, }, export: { filepath: '/Users/lucasdower/Documents/out.obj', // Must be an absolute path to the file (can be anywhere)