Added 'seed' option to Voxelise and Assign sections

This commit is contained in:
Lucas Dower 2023-01-26 22:51:06 +00:00
parent de67687d86
commit 5a401e9f1d
19 changed files with 188 additions and 8 deletions

24
package-lock.json generated
View File

@ -13,6 +13,7 @@
"jpeg-js": "^0.4.4",
"pngjs": "^6.0.0",
"prismarine-nbt": "^1.6.0",
"seedrandom": "^3.0.5",
"tga": "^1.0.7",
"twgl.js": "^4.19.1",
"varint-array": "^0.0.0"
@ -25,6 +26,7 @@
"@types/obj-file-parser": "^0.5.0",
"@types/pngjs": "^6.0.1",
"@types/prompt": "^1.1.2",
"@types/seedrandom": "^3.0.4",
"@types/sharp": "^0.31.0",
"@types/varint": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^5.9.1",
@ -1550,6 +1552,12 @@
"integrity": "sha512-q6KSi3PklLGQ0CesZ/XuLwly4DXXlnJuucYOG9lrBqrP8rKiuPZThav2h2+pFjaheNpnT0qKK3i304QWIePeJw==",
"dev": true
},
"node_modules/@types/seedrandom": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.4.tgz",
"integrity": "sha512-/rWdxeiuZenlawrHU+XV6ZHMTKOqrC2hMfeDfLTIWJhDZP5aVqXRysduYHBbhD7CeJO6FJr/D2uBVXB7GT6v7w==",
"dev": true
},
"node_modules/@types/sharp": {
"version": "0.31.0",
"resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.0.tgz",
@ -6722,6 +6730,11 @@
"node": ">=10"
}
},
"node_modules/seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -9163,6 +9176,12 @@
"integrity": "sha512-q6KSi3PklLGQ0CesZ/XuLwly4DXXlnJuucYOG9lrBqrP8rKiuPZThav2h2+pFjaheNpnT0qKK3i304QWIePeJw==",
"dev": true
},
"@types/seedrandom": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.4.tgz",
"integrity": "sha512-/rWdxeiuZenlawrHU+XV6ZHMTKOqrC2hMfeDfLTIWJhDZP5aVqXRysduYHBbhD7CeJO6FJr/D2uBVXB7GT6v7w==",
"dev": true
},
"@types/sharp": {
"version": "0.31.0",
"resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.0.tgz",
@ -13095,6 +13114,11 @@
"xmlchars": "^2.2.0"
}
},
"seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",

View File

@ -37,6 +37,7 @@
"@types/obj-file-parser": "^0.5.0",
"@types/pngjs": "^6.0.1",
"@types/prompt": "^1.1.2",
"@types/seedrandom": "^3.0.4",
"@types/sharp": "^0.31.0",
"@types/varint": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^5.9.1",
@ -62,6 +63,7 @@
"jpeg-js": "^0.4.4",
"pngjs": "^6.0.0",
"prismarine-nbt": "^1.6.0",
"seedrandom": "^3.0.5",
"tga": "^1.0.7",
"twgl.js": "^4.19.1",
"varint-array": "^0.0.0"

View File

@ -346,6 +346,7 @@ export class AppContext {
useMultisampleColouring: uiElements.multisampleColouring.getValue(),
enableAmbientOcclusion: uiElements.ambientOcclusion.getValue(),
voxelOverlapRule: uiElements.voxelOverlapRule.getValue(),
seed: uiElements.seed.getValue(),
},
};
@ -434,6 +435,7 @@ export class AppContext {
lightThreshold: uiElements.lightThreshold.getValue(),
contextualAveraging: uiElements.contextualAveraging.getValue(),
errorWeight: uiElements.errorWeight.getValue() / 10,
seed: uiElements.seed.getValue(),
},
};

View File

@ -1,4 +1,5 @@
import { AppConfig } from './config';
import { AppRandom } from './math';
import { TBrand } from './util/type_util';
export type RGBA = {
@ -17,9 +18,9 @@ export namespace RGBAUtil {
export function random(): RGBA {
return {
r: Math.random(),
g: Math.random(),
b: Math.random(),
r: AppRandom.Get.random(),
g: AppRandom.Get.random(),
b: AppRandom.Get.random(),
a: 1.0,
};
}

View File

@ -1,11 +1,12 @@
import { RGBA_255 } from './colour';
import { AppConfig } from './config';
import { AppRandom } from './math';
import { ASSERT } from './util/error_util';
import { Vector3 } from './vector';
export class Ditherer {
public static ditherRandom(colour: RGBA_255) {
const offset = (Math.random() - 0.5) * AppConfig.Get.DITHER_MAGNITUDE;
const offset = (AppRandom.Get.random() - 0.5) * AppConfig.Get.DITHER_MAGNITUDE;
colour.r += offset;
colour.g += offset;

View File

@ -1,7 +1,32 @@
import seedrandom from 'seedrandom';
import { AppError } from './util/error_util';
import { LOG_ERROR } from './util/log_util';
import { LOG, LOG_ERROR } from './util/log_util';
import { Vector3 } from './vector';
export class AppRandom {
/* Singleton */
private static _instance: AppRandom;
public static get Get() {
return this._instance || (this._instance = new this());
}
private _random: seedrandom.PRNG;
private constructor() {
this._random = seedrandom();
}
public init(seed: number) {
LOG(`[AppRandom]: Seeding with '${seed.toString()}'`);
this._random = seedrandom(seed.toString());
}
public random() {
return this._random.double();
}
}
export namespace AppMath {
export const RADIANS_0 = degreesToRadians(0.0);
export const RADIANS_90 = degreesToRadians(90.0);

View File

@ -0,0 +1,65 @@
import { ConfigUIElement } from './config_element';
export class NumberUIElement extends ConfigUIElement<number, HTMLInputElement> {
private _min: number;
private _max: number;
private _step: number;
private _hovering: boolean;
public constructor() {
super(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
this._min = 0;
this._max = 1;
this._step = 0.1;
this._hovering = false;
}
/**
* Set the minimum value the input can be set to.
*/
public setMin(min: number) {
this._min = min;
return this;
}
/**
* Set the maximum value the input can be set to.
*/
public setMax(max: number) {
this._max = max;
return this;
}
/**
* Set the number of steps to display the value to.
*/
public setStep(step: number) {
this._step = step;
return this;
}
public override registerEvents() {
this._getElement().addEventListener('change', () => {
this._setValue(parseInt(this._getElement().value));
});
}
public override _generateInnerHTML() {
return `
<input class="number-input" type="number" step="${this._step}" id="${this._getId()}" min="${this._min}" max="${this._max}" value="${this.getValue()}">
`;
}
protected override _onEnabledChanged() {
super._onEnabledChanged();
const element = this._getElement();
element.disabled = !this.getEnabled();
}
private _onTypedValue() {
}
protected _onValueChanged(): void {
}
}

View File

@ -20,6 +20,7 @@ import { ComboBoxElement, ComboBoxItem } from './elements/combobox';
import { ConfigUIElement } from './elements/config_element';
import { FileInputElement } from './elements/file_input';
import { HeaderUIElement } from './elements/header_element';
import { NumberUIElement } from './elements/number_input';
import { OutputElement } from './elements/output';
import { SliderElement } from './elements/slider';
import { ToolbarItemElement } from './elements/toolbar_item';
@ -129,6 +130,11 @@ export class UI {
tooltip: 'If multiple voxels are placed in the same location, use the first voxel\'s colour',
})
.setLabel('Voxel overlap'),
'seed': new NumberUIElement()
.setMin(-Infinity)
.setMax(Infinity)
.setStep(1)
.setLabel('Seed'),
},
elementsOrder: [
'constraintAxis',
@ -137,6 +143,7 @@ export class UI {
'ambientOcclusion',
'multisampleColouring',
'voxelOverlapRule',
'seed',
],
submitButton: new ButtonElement()
.setOnClick(() => {
@ -225,6 +232,11 @@ export class UI {
.setStep(1)
.setLabel('Light threshold')
.setShouldObeyGroupEnables(false),
'seed': new NumberUIElement()
.setMin(-Infinity)
.setMax(Infinity)
.setStep(1)
.setLabel('Seed'),
},
elementsOrder: [
'textureAtlas',
@ -236,6 +248,7 @@ export class UI {
'errorWeight',
'calculateLighting',
'lightThreshold',
'seed',
],
submitButton: new ButtonElement()
.setOnClick(() => {

View File

@ -1,4 +1,5 @@
import { IHashable } from './hash_map';
import { AppRandom } from './math';
import { ASSERT } from './util/error_util';
import { Vector3Hash } from './util/type_util';
@ -36,9 +37,9 @@ export class Vector3 implements IHashable {
static random() {
return new Vector3(
Math.random(),
Math.random(),
Math.random(),
AppRandom.Get.random(),
AppRandom.Get.random(),
AppRandom.Get.random(),
);
}

View File

@ -5,6 +5,7 @@ import { EAppEvent, EventManager } from './event';
import { IExporter } from './exporters/base_exporter';
import { ExporterFactory } from './exporters/exporters';
import { ObjImporter } from './importers/obj_importer';
import { AppRandom } from './math';
import { Mesh } from './mesh';
import { ProgressManager, TTaskHandle } from './progress';
import { ASSERT } from './util/error_util';
@ -106,6 +107,7 @@ export class WorkerClient {
public voxelise(params: VoxeliseParams.Input): VoxeliseParams.Output {
ASSERT(this._loadedMesh !== undefined);
AppRandom.Get.init(params.seed);
const voxeliser: IVoxeliser = VoxeliserFactory.GetVoxeliser(params.voxeliser);
this._loadedVoxelMesh = voxeliser.voxelise(this._loadedMesh, params);
@ -150,6 +152,7 @@ export class WorkerClient {
public assign(params: AssignParams.Input): AssignParams.Output {
ASSERT(this._loadedVoxelMesh !== undefined);
AppRandom.Get.init(params.seed);
this._loadedBlockMesh = BlockMesh.createFromVoxelMesh(this._loadedVoxelMesh, params);

View File

@ -63,6 +63,7 @@ export namespace VoxeliseParams {
useMultisampleColouring: boolean,
enableAmbientOcclusion: boolean,
voxelOverlapRule: TVoxelOverlapRule,
seed: number,
}
export type Output = {
@ -115,6 +116,7 @@ export namespace AssignParams {
lightThreshold: number,
contextualAveraging: boolean,
errorWeight: number,
seed: number,
}
export type Output = {

View File

@ -849,4 +849,13 @@ svg {
a {
font-weight: 400;
color: var(--prop-accent-hovered)
}
.number-input {
width: 100%;
height: calc(var(--property-height) - 2px);
text-align: start;
padding: 0px 0px 0px 10px;
margin: 0px;
}

View File

@ -23,6 +23,7 @@ test('Voxelise solid 2x2 cube', () => {
enableAmbientOcclusion: false,
voxelOverlapRule: 'average',
voxeliser: 'ncrb',
seed: 0,
});
const expectedVoxels = [

View File

@ -16,6 +16,7 @@ const baseConfig: THeadlessConfig = {
useMultisampleColouring: false,
voxelOverlapRule: 'average',
enableAmbientOcclusion: false, // Only want true if exporting to .obj
seed: 0,
},
assign: {
textureAtlas: 'vanilla', // Must be an atlas name that exists in /resources/atlases
@ -28,6 +29,7 @@ const baseConfig: THeadlessConfig = {
lightThreshold: 0,
contextualAveraging: true,
errorWeight: 0.0,
seed: 0,
},
export: {
filepath: '', // Must be an absolute path to the file (can be anywhere)

View File

@ -16,6 +16,7 @@ const baseConfig: THeadlessConfig = {
useMultisampleColouring: false,
voxelOverlapRule: 'average',
enableAmbientOcclusion: false, // Only want true if exporting to .obj
seed: 0,
},
assign: {
textureAtlas: 'vanilla', // Must be an atlas name that exists in /resources/atlases
@ -28,6 +29,7 @@ const baseConfig: THeadlessConfig = {
lightThreshold: 0,
contextualAveraging: true,
errorWeight: 0.0,
seed: 0,
},
export: {
filepath: '', // Must be an absolute path to the file (can be anywhere)

View File

@ -16,6 +16,7 @@ const baseConfig: THeadlessConfig = {
useMultisampleColouring: false,
voxelOverlapRule: 'average',
enableAmbientOcclusion: false, // Only want true if exporting to .obj
seed: 0,
},
assign: {
textureAtlas: 'vanilla', // Must be an atlas name that exists in /resources/atlases
@ -28,6 +29,7 @@ const baseConfig: THeadlessConfig = {
lightThreshold: 0,
contextualAveraging: true,
errorWeight: 0.0,
seed: 0,
},
export: {
filepath: '', // Must be an absolute path to the file (can be anywhere)

View File

@ -16,6 +16,7 @@ const baseConfig: THeadlessConfig = {
useMultisampleColouring: false,
voxelOverlapRule: 'average',
enableAmbientOcclusion: false, // Only want true if exporting to .obj
seed: 0,
},
assign: {
textureAtlas: 'vanilla', // Must be an atlas name that exists in /resources/atlases
@ -28,6 +29,7 @@ const baseConfig: THeadlessConfig = {
lightThreshold: 0,
contextualAveraging: true,
errorWeight: 0.0,
seed: 0,
},
export: {
filepath: '', // Must be an absolute path to the file (can be anywhere)

21
tests/random.test.ts Normal file
View File

@ -0,0 +1,21 @@
import { AppRandom } from '../src/math';
import { TEST_PREAMBLE } from './preamble';
test('Seeded Random', () => {
TEST_PREAMBLE();
AppRandom.Get.init(0);
expect(AppRandom.Get.random()).toEqual(0.7803563384230067);
expect(AppRandom.Get.random()).toEqual(0.05144520047760746);
expect(AppRandom.Get.random()).toEqual(0.5317574394273067);
AppRandom.Get.init(12345);
expect(AppRandom.Get.random()).toEqual(0.20703519639616447);
expect(AppRandom.Get.random()).toEqual(0.6602710883040208);
expect(AppRandom.Get.random()).toEqual(0.500949276170095);
AppRandom.Get.init(0);
expect(AppRandom.Get.random()).toEqual(0.7803563384230067);
expect(AppRandom.Get.random()).toEqual(0.05144520047760746);
expect(AppRandom.Get.random()).toEqual(0.5317574394273067);
});

View File

@ -14,6 +14,7 @@ export const headlessConfig: THeadlessConfig = {
useMultisampleColouring: false,
voxelOverlapRule: 'average',
enableAmbientOcclusion: false, // Only want true if exporting to .obj
seed: 0,
},
assign: {
textureAtlas: 'vanilla', // Must be an atlas name that exists in /resources/atlases
@ -26,6 +27,7 @@ export const headlessConfig: THeadlessConfig = {
lightThreshold: 0,
contextualAveraging: true,
errorWeight: 0.0,
seed: 0,
},
export: {
filepath: '/Users/lucasdower/Documents/out.obj', // Must be an absolute path to the file (can be anywhere)