forked from mirror/ObjToSchematic
Added 'seed' option to Voxelise and Assign sections
This commit is contained in:
parent
de67687d86
commit
5a401e9f1d
24
package-lock.json
generated
24
package-lock.json
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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(),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
|
27
src/math.ts
27
src/math.ts
@ -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);
|
||||
|
65
src/ui/elements/number_input.ts
Normal file
65
src/ui/elements/number_input.ts
Normal 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 {
|
||||
}
|
||||
}
|
@ -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(() => {
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
}
|
@ -23,6 +23,7 @@ test('Voxelise solid 2x2 cube', () => {
|
||||
enableAmbientOcclusion: false,
|
||||
voxelOverlapRule: 'average',
|
||||
voxeliser: 'ncrb',
|
||||
seed: 0,
|
||||
});
|
||||
|
||||
const expectedVoxels = [
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
21
tests/random.test.ts
Normal 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);
|
||||
});
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user