Skip to content

Commit 434fca0

Browse files
committed
Convert preview scenes to typescript
1 parent 91133d0 commit 434fca0

6 files changed

Lines changed: 194 additions & 169 deletions

File tree

js/interface/dialog.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,7 @@ type MessageBoxCommandOptions = string | {
884884
}
885885
type MessageBoxCheckbox = string | {
886886
value?: boolean
887-
condition: ConditionResolvable
887+
condition?: ConditionResolvable
888888
text: string
889889
}
890890
export interface MessageBoxOptions {
Lines changed: 192 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,44 @@ import { adjustFromAndToForInflateAndStretch } from "../outliner/types/cube";
33
import { compileJSON } from "../util/json";
44
import { toSnakeCase } from "../util/util";
55

6+
export interface PreviewSceneOptions {
7+
name?: string
8+
description?: string
9+
category?: string
10+
web_config?: string
11+
require_minecraft_eula?: boolean
12+
light_color?: {r: number, g: number, b: number}
13+
light_side?: number
14+
condition?: ConditionResolvable
15+
cubemap?: string[]
16+
fog?: {
17+
type: 'linear' | 'exponential'
18+
color
19+
near?: number
20+
far?: number
21+
density?: number
22+
}
23+
fov?: number
24+
preview_models?: (string | PreviewModel | PreviewModelOptions)[]
25+
}
26+
627
export class PreviewScene {
7-
constructor(id, data = 0) {
28+
id: string
29+
loaded: boolean = false;
30+
require_minecraft_eula: boolean = false;
31+
name: string;
32+
description?: string;
33+
category: string;
34+
light_color: {r: number, g: number, b: number} = {r: 1, g: 1, b: 1};
35+
light_side: number = 0;
36+
condition: ConditionResolvable;
37+
fov: number = null;
38+
web_config_path?: string
39+
fog?: THREE.Fog | THREE.FogExp2
40+
cubemap?: THREE.CubeTexture
41+
preview_models = [];
42+
43+
constructor(id: string, data: PreviewSceneOptions = {}) {
844
PreviewScene.scenes[id] = this;
945
this.id = id;
1046
this.loaded = false;
@@ -24,7 +60,7 @@ export class PreviewScene {
2460

2561
PreviewScene.menu_categories[this.category][id] = this.name;
2662
}
27-
extend(data) {
63+
extend(data: PreviewSceneOptions) {
2864
this.loaded = data.web_config ? false : true;
2965
this.web_config_path = data.web_config;
3066
if (data.require_minecraft_eula) this.require_minecraft_eula = true;
@@ -47,6 +83,7 @@ export class PreviewScene {
4783
Canvas.updateShading();
4884
}
4985
});
86+
// @ts-expect-error
5087
texture_cube.colorSpace = THREE.SRGBColorSpace;
5188
texture_cube.mapping = THREE.CubeRefractionMapping;
5289
this.cubemap = texture_cube;
@@ -99,6 +136,9 @@ export class PreviewScene {
99136
}
100137
this.extend(json);
101138
}
139+
/**
140+
* Selects this preview scene
141+
*/
102142
async select() {
103143
if (this.require_minecraft_eula) {
104144
let accepted = await MinecraftEULA.promptUser('preview_scenes');
@@ -109,7 +149,7 @@ export class PreviewScene {
109149
}
110150
if (PreviewScene.active) PreviewScene.active.unselect();
111151

112-
Canvas.global_light_color.copy(this.light_color);
152+
Canvas.global_light_color.copy(this.light_color as THREE.Color);
113153
Canvas.global_light_side = this.light_side;
114154
Canvas.scene.background = this.cubemap;
115155
Canvas.scene.fog = this.fog;
@@ -128,6 +168,9 @@ export class PreviewScene {
128168
Blockbench.dispatchEvent('select_preview_scene', {scene: this});
129169
Canvas.updateShading();
130170
}
171+
/**
172+
* Unselects this preview scene
173+
*/
131174
unselect() {
132175
this.preview_models.forEach(model => {
133176
model.disable();
@@ -138,7 +181,7 @@ export class PreviewScene {
138181
if (this.cubemap) scene.background = null;
139182
if (this.fog) scene.fog = null;
140183
if (this.fov && !(Modes.display && DisplayMode.display_slot.startsWith('firstperson'))) {
141-
Preview.all.forEach(preview => preview.setFOV(settings.fov.value));
184+
Preview.all.forEach(preview => preview.setFOV(settings.fov.value as number));
142185
}
143186
Blockbench.dispatchEvent('unselect_preview_scene', {scene: this});
144187
Canvas.updateShading();
@@ -148,28 +191,109 @@ export class PreviewScene {
148191
delete PreviewScene.scenes[this.id];
149192
delete PreviewScene.menu_categories[this.category][this.id];
150193
}
194+
195+
/**
196+
* All preview scenes, listed by ID
197+
*/
198+
static scenes: Record<string, PreviewScene> = {};
199+
/**
200+
* The currently active scene
201+
*/
202+
static active: PreviewScene | null = null;
203+
static select_options = {};
204+
/**
205+
* The URL to the source repository that scenes are pulled from
206+
*/
207+
static source_repository = 'https://cdn.jsdelivr.net/gh/JannisX11/blockbench-scenes';
208+
static menu_categories: {
209+
[category_id: string]: {
210+
[id: string]: string
211+
}
212+
} = {
213+
main: {
214+
none: tl('generic.none')
215+
},
216+
generic: {
217+
_label: 'Generic'
218+
},
219+
realistic: {
220+
_label: 'Realistic'
221+
},
222+
minecraft: {
223+
_label: 'Minecraft'
224+
},
225+
};
226+
151227
}
152-
PreviewScene.scenes = {};
153-
PreviewScene.active = null;
154-
PreviewScene.select_options = {};
155-
PreviewScene.source_repository = 'https://cdn.jsdelivr.net/gh/JannisX11/blockbench-scenes';
156-
PreviewScene.menu_categories = {
157-
main: {
158-
none: tl('generic.none')
159-
},
160-
generic: {
161-
_label: 'Generic'
162-
},
163-
realistic: {
164-
_label: 'Realistic'
165-
},
166-
minecraft: {
167-
_label: 'Minecraft'
168-
},
169-
};
170228

171-
export class PreviewModel {
172-
constructor(id, data) {
229+
interface PreviewModelCubeTemplate {
230+
prefab?: string
231+
offset?: ArrayVector3
232+
offset_space?: 'block' | 'pixel'
233+
position?: ArrayVector3
234+
size?: ArrayVector3
235+
origin?: ArrayVector3
236+
rotation?: ArrayVector3
237+
faces?: {
238+
north?: { uv: ArrayVector4 }
239+
east?: { uv: ArrayVector4 }
240+
west?: { uv: ArrayVector4 }
241+
south?: { uv: ArrayVector4 }
242+
up?: { uv: ArrayVector4 }
243+
down?: { uv: ArrayVector4 }
244+
}
245+
}
246+
type PrefabTemplate = PreviewModelCubeTemplate
247+
export interface PreviewModelOptions {
248+
id?: string
249+
condition?: ConditionResolvable
250+
cubes?: PreviewModelCubeTemplate[]
251+
prefabs?: Record<string, PrefabTemplate>
252+
/**
253+
* Source of the model's texture
254+
*/
255+
texture?: string
256+
/**
257+
* Model tint color
258+
*/
259+
color?: string
260+
/**
261+
* Enable shading on the material
262+
*/
263+
shading?: boolean
264+
/**
265+
* THREE.JS material render side
266+
*/
267+
render_side?: number
268+
texture_size?: [number, number]
269+
position?: ArrayVector3
270+
rotation?: ArrayVector3
271+
scale?: ArrayVector3
272+
onUpdate?(): void
273+
}
274+
export class PreviewModel implements Deletable {
275+
id: string
276+
condition: ConditionResolvable
277+
model_3d: THREE.Object3D
278+
onUpdate?: () => void
279+
enabled: boolean = false;
280+
build_data: {
281+
prefabs?: Record<string, PrefabTemplate>,
282+
cubes?: PreviewModelCubeTemplate[],
283+
texture?: string
284+
position?: ArrayVector3
285+
rotation?: ArrayVector3
286+
scale?: ArrayVector3
287+
}
288+
color: string
289+
shading: boolean
290+
render_side: THREE.Side
291+
texture_size: ArrayVector2
292+
cubes: PreviewModelCubeTemplate[]
293+
texture?: string
294+
[idkey: string]: any
295+
296+
constructor(id: string, data: PreviewModelOptions) {
173297
PreviewModel.models[id] = this;
174298
this.id = id;
175299
this.condition = data.condition;
@@ -192,15 +316,24 @@ export class PreviewModel {
192316

193317
this.buildModel();
194318
}
319+
/**
320+
* Enables the model in the preview
321+
*/
195322
enable() {
196323
Canvas.scene.add(this.model_3d);
197324
this.enabled = true;
198325
this.update();
199326
}
327+
/**
328+
* Disables the model in the preview
329+
*/
200330
disable() {
201331
Canvas.scene.remove(this.model_3d);
202332
this.enabled = false;
203333
}
334+
/**
335+
* Update the appearance and visibility of the model
336+
*/
204337
update() {
205338
if (typeof this.onUpdate == 'function') {
206339
this.onUpdate();
@@ -217,7 +350,7 @@ export class PreviewModel {
217350
this.model_3d.visible = !!Condition(this.condition);
218351
}
219352
buildModel() {
220-
let tex;
353+
let tex: THREE.Texture = undefined;
221354
if (this.build_data.texture) {
222355
let img = new Image();
223356
img.src = this.build_data.texture;
@@ -262,6 +395,7 @@ export class PreviewModel {
262395
}
263396
mesh.geometry.translate(cube.position[0] + cube.size[0]/2, cube.position[1] + cube.size[1]/2, cube.position[2] + cube.size[2]/2)
264397
if (cube.rotation) {
398+
// @ts-expect-error
265399
mesh.rotation.setFromDegreeArray(cube.rotation)
266400
}
267401

@@ -285,11 +419,13 @@ export class PreviewModel {
285419
}
286420

287421
let indices = [];
422+
// @ts-expect-error
288423
mesh.geometry.faces = [];
289424
mesh.geometry.clearGroups();
290425
Canvas.face_order.forEach((fkey, i) => {
291426
if (cube.faces[fkey]) {
292427
indices.push(0 + i*4, 2 + i*4, 1 + i*4, 2 + i*4, 3 + i*4, 1 + i*4);
428+
// @ts-expect-error
293429
mesh.geometry.faces.push(fkey)
294430
}
295431
})
@@ -306,11 +442,12 @@ export class PreviewModel {
306442
case 'up': fIndex = 4; break;
307443
case 'down': fIndex = 6; break;
308444
}
309-
mesh.geometry.attributes.uv.array.set(uv_array[0], fIndex*4 + 0); //0,1
310-
mesh.geometry.attributes.uv.array.set(uv_array[1], fIndex*4 + 2); //1,1
311-
mesh.geometry.attributes.uv.array.set(uv_array[2], fIndex*4 + 4); //0,0
312-
mesh.geometry.attributes.uv.array.set(uv_array[3], fIndex*4 + 6); //1,0
313-
mesh.geometry.attributes.uv.needsUpdate = true;
445+
let uv_attr = mesh.geometry.attributes.uv;
446+
(uv_attr.array as any).set(uv_array[0], fIndex*4 + 0); //0,1
447+
(uv_attr.array as any).set(uv_array[1], fIndex*4 + 2); //1,1
448+
(uv_attr.array as any).set(uv_array[2], fIndex*4 + 4); //0,0
449+
(uv_attr.array as any).set(uv_array[3], fIndex*4 + 6); //1,0
450+
uv_attr.needsUpdate = true;
314451
}
315452

316453
this.model_3d.add(mesh);
@@ -323,8 +460,8 @@ export class PreviewModel {
323460

324461
static generateModelFromProject() {
325462
let cubes = Cube.all.map(cube => {
326-
let from = cube.from.slice();
327-
let to = cube.to.slice();
463+
let from = cube.from.slice() as ArrayVector3;
464+
let to = cube.to.slice() as ArrayVector3;
328465
adjustFromAndToForInflateAndStretch(from, to, cube);
329466
let data = {
330467
"position": from,
@@ -356,17 +493,17 @@ export class PreviewModel {
356493
cubes
357494
});
358495
}
359-
}
360-
PreviewModel.models = {};
361-
PreviewModel.getActiveModels = function() {
362-
let list = [];
363-
for (let id in PreviewModel.models) {
364-
let model = PreviewModel.models[id];
365-
if (model.enabled) {
366-
list.push(model);
496+
static models: Record<string, PreviewModel> = {};
497+
static getActiveModels = function(): PreviewModel[] {
498+
let list = [];
499+
for (let id in PreviewModel.models) {
500+
let model = PreviewModel.models[id];
501+
if (model.enabled) {
502+
list.push(model);
503+
}
367504
}
505+
return list;
368506
}
369-
return list;
370507
}
371508

372509
new PreviewModel('studio', {
@@ -767,10 +904,10 @@ player_preview_model.updateArmVariant = function(slim) {
767904

768905
StateMemory.init('minecraft_eula_accepted', 'object');
769906
export const MinecraftEULA = {
770-
isAccepted(key) {
907+
isAccepted(key: string) {
771908
return StateMemory.minecraft_eula_accepted[key];
772909
},
773-
async promptUser(key) {
910+
async promptUser(key: string) {
774911
if (MinecraftEULA.isAccepted(key)) {
775912
return true;
776913
}
@@ -802,7 +939,7 @@ BARS.defineActions(function() {
802939
category: 'view',
803940
icon: 'nature_people',
804941
click(event) {
805-
new Menu(this.children).show(event.target);
942+
new Menu(this.children).show(event.target as HTMLElement);
806943
},
807944
children: () => {
808945
let list = [];
@@ -846,9 +983,18 @@ BARS.defineActions(function() {
846983
})
847984
})
848985

849-
Object.assign(window, {
986+
const global = {
850987
PreviewScene,
851988
PreviewModel,
852989
MinecraftEULA,
853990
player_preview_model
854-
})
991+
};
992+
declare global {
993+
type PreviewScene = import('./preview_scenes').PreviewScene
994+
const PreviewScene: typeof global.PreviewScene
995+
type PreviewModel = import('./preview_scenes').PreviewModel
996+
const PreviewModel: typeof global.PreviewModel
997+
const MinecraftEULA: typeof global.MinecraftEULA
998+
const player_preview_model: PreviewModel
999+
}
1000+
Object.assign(window, global);

0 commit comments

Comments
 (0)