176 lines
5.0 KiB
TypeScript
176 lines
5.0 KiB
TypeScript
import { makeAutoObservable, runInAction } from "mobx";
|
|
import type { WorldState } from "./worldState";
|
|
import { type ObjectInstance, type Pos3, type R3, type V3, type RuntimeScene, type ObjectType } from "../types";
|
|
import { createObjectInstance } from "../utils/object";
|
|
import { randomId } from "../utils";
|
|
import { state } from "./rootState";
|
|
import { populateRuntimeObject } from "../utils/runtime";
|
|
import { DEFAULT_VOXEL_TYPE } from "../model/defaultVoxelTypes";
|
|
|
|
export const ObjectEditModeEnum = [
|
|
'translate',
|
|
'rotate',
|
|
'scale',
|
|
] as const;
|
|
type ObjectEditModeTuple = typeof ObjectEditModeEnum;
|
|
export type ObjectEditMode = ObjectEditModeTuple[number];
|
|
|
|
export function nextObjectEditMode(mode: ObjectEditMode | undefined): ObjectEditMode {
|
|
if (mode === undefined)
|
|
return ObjectEditModeEnum[0];
|
|
|
|
const idx = ObjectEditModeEnum.indexOf(mode);
|
|
return ObjectEditModeEnum[(idx + 1) % ObjectEditModeEnum.length];
|
|
}
|
|
|
|
export const ObjectTypeEditModeEnum = [
|
|
'scripts',
|
|
] as const;
|
|
type ObjectTypeEditModeTuple = typeof ObjectTypeEditModeEnum;
|
|
export type ObjectTypeEditMode = ObjectTypeEditModeTuple[number];
|
|
|
|
export function nextObjectTypeEditMode(mode: ObjectTypeEditMode | undefined): ObjectTypeEditMode {
|
|
if (mode === undefined)
|
|
return ObjectTypeEditModeEnum[0];
|
|
|
|
const idx = ObjectTypeEditModeEnum.indexOf(mode);
|
|
return ObjectTypeEditModeEnum[(idx + 1) % ObjectTypeEditModeEnum.length];
|
|
}
|
|
|
|
export type SelectedObject = {
|
|
type: 'object',
|
|
id: string;
|
|
editMode: ObjectEditMode;
|
|
}
|
|
|
|
export type SelectedObjectType = {
|
|
type: 'objectType',
|
|
id: string;
|
|
editMode: ObjectTypeEditMode;
|
|
}
|
|
|
|
export type Selection = SelectedObject | SelectedObjectType;
|
|
|
|
export class WorldEditorState {
|
|
private readonly world: WorldState;
|
|
|
|
public selection: Selection | undefined;
|
|
|
|
constructor(world: WorldState) {
|
|
this.world = world;
|
|
makeAutoObservable(this);
|
|
}
|
|
|
|
public get isEnabled(): boolean {
|
|
return !state.game;
|
|
}
|
|
|
|
public setCamera(value: Pos3): void {
|
|
this.world.data.editorCamera = value;
|
|
}
|
|
|
|
public setSelection(value: Selection | undefined): void {
|
|
this.selection = value;
|
|
}
|
|
|
|
public setSelectedObject(value: Omit<SelectedObject, 'type'> | undefined): void {
|
|
this.setSelection(value
|
|
? {
|
|
type: 'object',
|
|
...value,
|
|
}
|
|
: undefined,
|
|
);
|
|
}
|
|
|
|
public setSelectedObjectType(value: Omit<SelectedObjectType, 'type'> | undefined): void {
|
|
this.setSelection(value
|
|
? {
|
|
type: 'objectType',
|
|
...value,
|
|
}
|
|
: undefined,
|
|
);
|
|
}
|
|
|
|
public resetSelection() {
|
|
this.setSelection(undefined);
|
|
}
|
|
|
|
public get scene(): RuntimeScene {
|
|
return this.world.data.initialScene;
|
|
}
|
|
|
|
public get camera(): Pos3 {
|
|
return this.world.data.editorCamera;
|
|
}
|
|
|
|
public setObjectTransform(id: string, position: V3, rotation: R3, scale: V3): void {
|
|
const obj = this.scene.objects[id];
|
|
if (!obj)
|
|
return;
|
|
|
|
runInAction(() => { // all in one go
|
|
obj.position = position;
|
|
obj.rotation = rotation;
|
|
obj.scale = scale;
|
|
});
|
|
}
|
|
|
|
public addObjectInstanceAtRandomPosition(typeId: string): ObjectInstance {
|
|
return this.addObjectInstance(
|
|
typeId,
|
|
{ x: Math.random() * 3, y: Math.random() * 3, z: Math.random() * 1 },
|
|
);
|
|
}
|
|
|
|
public addObjectInstance(
|
|
typeId: string,
|
|
pos: { x: number; y: number; z: number; },
|
|
): ObjectInstance {
|
|
const obj = populateRuntimeObject(createObjectInstance(randomId(), typeId), this.world.data);
|
|
obj.position = [pos.x, pos.y, pos.z];
|
|
obj.rotation = [0, 0, 0];
|
|
|
|
this.scene.objects[obj.id] = obj;
|
|
|
|
this.setSelectedObject({ id: obj.id, editMode: 'translate' });
|
|
|
|
return obj;
|
|
}
|
|
|
|
public deleteObject(objectId: string) {
|
|
if (this.selection?.type === 'object' && this.selection.id === objectId)
|
|
this.resetSelection();
|
|
delete (this.scene.objects[objectId]);
|
|
}
|
|
|
|
public addObjectType(): ObjectType {
|
|
const objectType: ObjectType = {
|
|
id: randomId(),
|
|
name: 'Unnamed',
|
|
voxels: [
|
|
{
|
|
typeId: DEFAULT_VOXEL_TYPE.id,
|
|
position: [0, 0, 0],
|
|
}
|
|
],
|
|
};
|
|
this.world.data.objectTypes[objectType.id] = objectType;
|
|
this.setSelectedObjectType({ id: objectType.id, editMode: 'scripts' });
|
|
return objectType;
|
|
}
|
|
|
|
public deleteObjectType(typeId: string) {
|
|
if (this.selection?.type === 'objectType' && this.selection.id === typeId)
|
|
this.resetSelection();
|
|
|
|
for (const id in this.scene.objects)
|
|
if (this.scene.objects[id].typeId === typeId)
|
|
this.deleteObject(id);
|
|
|
|
delete (this.world.data.objectTypes[typeId])
|
|
}
|
|
}
|
|
|