workspaces folders
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
import type { WorldState } from "./worldState";
|
||||
import { type ObjectInstance, type Pos3, type R3, type V3, type RuntimeScene, type ObjectType, type Voxel } from "../types";
|
||||
import { createObjectInstance } from "../utils/object";
|
||||
import { randomId } from "../utils";
|
||||
import { state } from "./rootState";
|
||||
import { populateRuntimeObject, computeBoundingBox } from "../utils/runtime";
|
||||
import { DEFAULT_VOXEL_TYPE } from "../model/defaultVoxelTypes";
|
||||
import { getObjectVoxelGroups } from "../utils/graphics/voxelGroup";
|
||||
import { buildObjectTrimesh } from "../utils/graphics/mesh";
|
||||
|
||||
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 addVoxelToObjectType(typeId: string, voxel: Voxel): void {
|
||||
const objectType = this.world.getObjectTypeById(typeId);
|
||||
if (!objectType)
|
||||
return;
|
||||
const occupied = objectType.voxels.some(v =>
|
||||
v.position[0] === voxel.position[0] &&
|
||||
v.position[1] === voxel.position[1] &&
|
||||
v.position[2] === voxel.position[2]
|
||||
);
|
||||
if (!occupied)
|
||||
objectType.voxels.push(voxel);
|
||||
}
|
||||
|
||||
public removeVoxelFromObjectType(typeId: string, position: V3): void {
|
||||
const objectType = this.world.getObjectTypeById(typeId);
|
||||
if (!objectType)
|
||||
return;
|
||||
const idx = objectType.voxels.findIndex(v =>
|
||||
v.position[0] === position[0] &&
|
||||
v.position[1] === position[1] &&
|
||||
v.position[2] === position[2]
|
||||
);
|
||||
if (idx !== -1)
|
||||
objectType.voxels.splice(idx, 1);
|
||||
}
|
||||
|
||||
public refreshObjectTypeCaches(typeId: string): void {
|
||||
const objectType = this.world.getObjectTypeById(typeId);
|
||||
if (!objectType)
|
||||
return;
|
||||
const world = this.world.data;
|
||||
const now = Date.now();
|
||||
for (const obj of Object.values(this.scene.objects)) {
|
||||
if (obj.typeId !== typeId)
|
||||
continue;
|
||||
obj.cache.voxelGroups = getObjectVoxelGroups(objectType, world.voxelTypes);
|
||||
obj.cache.colliderMesh = buildObjectTrimesh(objectType, world.voxelTypes);
|
||||
obj.cache.boundingBox = computeBoundingBox(objectType);
|
||||
obj.cache.updatedAt = now;
|
||||
}
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user