This commit is contained in:
Evert Prants 2023-06-03 22:25:21 +03:00
parent 4fde9cf8f0
commit 3fcd1b0385
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
18 changed files with 286 additions and 100 deletions

View File

@ -1,35 +1,39 @@
import { EventEmitter, GameRunner, Renderer } from '@freeblox/engine';
import { EditorViewport } from './viewport';
import {
EnvironmentComponent,
MouseComponent,
ViewportComponent,
EventEmitter,
GameRunner,
Renderer,
} from '@freeblox/engine';
import { EditorEvents } from '../types/events';
import { EditorWorkspace } from './workspace';
import { EditorMouse } from './mouse';
import { EditorEnvironment } from './environment';
import { WorkspaceComponent } from './workspace';
export class Editor extends GameRunner {
public lastTick = performance.now();
public events = new EventEmitter<EditorEvents>();
public render!: Renderer;
public element!: HTMLElement;
public viewport!: EditorViewport;
public workspace!: EditorWorkspace;
public mouse!: EditorMouse;
public environment!: EditorEnvironment;
public viewport!: ViewportComponent;
public workspace!: WorkspaceComponent;
public mouse!: MouseComponent;
public environment!: EnvironmentComponent;
public running = false;
override mount(element: HTMLElement) {
this.element = element;
this.render = new Renderer(element);
this.viewport = new EditorViewport(this.render, this.events);
this.viewport = new ViewportComponent(this.render, this.events);
this.viewport.initialize();
this.workspace = new EditorWorkspace(this.render, this.events);
this.workspace = new WorkspaceComponent(this.render, this.events);
this.workspace.initialize();
this.mouse = new EditorMouse(this.render, this.events);
this.mouse = new MouseComponent(this.render, this.events);
this.mouse.initialize();
this.environment = new EditorEnvironment(this.render, this.events);
this.environment = new EnvironmentComponent(this.render, this.events);
this.environment.initialize();
this.start();

View File

@ -1,28 +0,0 @@
import { EngineComponent, EventEmitter, Renderer } from '@freeblox/engine';
import { EditorEvents } from '../types/events';
import { AmbientLight, DirectionalLight } from 'three';
export class EditorEnvironment extends EngineComponent {
public ambient!: AmbientLight;
public directional!: DirectionalLight;
constructor(
protected renderer: Renderer,
protected events: EventEmitter<EditorEvents>
) {
super(renderer, events);
}
initialize(): void {
this.ambient = new AmbientLight(0x8a8a8a, 1.0);
this.directional = new DirectionalLight(0xffffff, 1);
this.directional.position.set(1, 1, 1);
this.renderer.scene.add(this.ambient);
this.renderer.scene.add(this.directional);
}
update(delta: number): void {}
cleanUp(): void {}
}

View File

@ -1,28 +1,27 @@
import { EngineComponent, EventEmitter, Renderer } from '@freeblox/engine';
import {
Brick,
Cylinder,
EngineComponent,
EventEmitter,
GameObject,
MouseButtonEvent,
Renderer,
Sphere,
} from '@freeblox/engine';
import {
AxesHelper,
BoxGeometry,
BoxHelper,
Color,
Euler,
GridHelper,
Material,
Mesh,
MeshPhongMaterial,
Object3D,
Vector3,
} from 'three';
import {
EditorEvents,
MouseButtonEvent,
MouseMoveEvent,
SelectEvent,
TransformModeEvent,
} from '../types/events';
import { EditorEvents, SelectEvent, TransformModeEvent } from '../types/events';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
export class EditorWorkspace extends EngineComponent {
export class WorkspaceComponent extends EngineComponent {
public background = new Object3D();
public world = new Object3D();
public helpers = new Object3D();
@ -70,9 +69,13 @@ export class EditorWorkspace extends EngineComponent {
this.helpers.name = '_helper';
scene.add(this.background, this.world, this.helpers);
const test = new Mesh(new BoxGeometry(), new MeshPhongMaterial());
const test = new Brick();
test.position.set(2, 2, 2);
this.world.add(test);
const test2 = new Cylinder();
test2.position.set(0, 2, 2);
const test3 = new Sphere();
test3.position.set(0, 2, 0);
this.world.add(test, test2, test3);
}
private removeFromScene(scene: Object3D) {
@ -81,16 +84,26 @@ export class EditorWorkspace extends EngineComponent {
private initializeSelector() {
let moved = false;
let clicked = false;
let casterDebounce: ReturnType<typeof setTimeout> | undefined;
const mouseDownEventHandler = () => {
moved = false;
clicked = true;
};
const mouseMoveEventHandler = () => {
moved = true;
if (casterDebounce || !clicked) return;
// Prevent miniscule mouse movement when clicking from preventing a selection
casterDebounce = setTimeout(() => {
moved = true;
}, 100);
};
const mouseUpEventHandler = (event: MouseButtonEvent) => {
clearTimeout(casterDebounce);
casterDebounce = undefined;
clicked = false;
if (moved) return;
if (!event.target?.object) {
if (!this.selection.length) return;
@ -106,7 +119,13 @@ export class EditorWorkspace extends EngineComponent {
return;
}
const object = event.target!.object;
let object = event.target!.object;
if (
!(object instanceof GameObject) &&
object.parent instanceof GameObject
) {
object = object.parent;
}
if (this.selection.includes(object)) {
if (event.control) {
const index = this.selection.indexOf(object);

View File

@ -1,25 +1,5 @@
import { EngineEvents } from '@freeblox/engine';
import { Euler, Intersection, Object3D, Vector2, Vector3 } from 'three';
export interface MousePositionEvent {
position: Vector2;
positionGL: Vector2;
}
export interface MouseMoveEvent extends MousePositionEvent {
offset: Vector2;
offsetGL: Vector2;
helper?: boolean;
}
export interface MouseButtonEvent extends MousePositionEvent {
button: number;
helper?: boolean;
target?: Intersection<Object3D>;
shift?: boolean;
control?: boolean;
alt?: boolean;
}
import { Euler, Object3D, Vector3 } from 'three';
export interface TransformEvent {
object: Object3D;
@ -52,10 +32,6 @@ export interface SelectEvent {
}
export type Events = {
error: (error: Error) => void;
mouseDown: (event: MouseButtonEvent) => void;
mouseUp: (event: MouseButtonEvent) => void;
mouseMove: (event: MouseMoveEvent) => void;
transformStart: (event: TransformEvent) => void;
transformChange: (event: TransformCompleteEvent) => void;
transformEnd: (event: TransformEvent) => void;

View File

@ -0,0 +1,58 @@
import { AmbientLight, Color, DirectionalLight } from 'three';
import { EngineEvents, EnvironmentEvent } from '../types/events';
import { EngineComponent } from '../types/engine-component';
import { Renderer } from '../core/renderer';
import { EventEmitter } from '../utils/events';
export class EnvironmentComponent extends EngineComponent {
public ambient!: AmbientLight;
public directional!: DirectionalLight;
private handlerCleanUp?: Function;
constructor(
protected renderer: Renderer,
protected events: EventEmitter<EngineEvents>
) {
super(renderer, events);
}
initialize(): void {
this.renderer.renderer.setClearColor(0x00aaff);
this.ambient = new AmbientLight(0x8a8a8a, 1.0);
this.directional = new DirectionalLight(0xffffff, 1);
this.directional.position.set(1, 1, 1);
this.renderer.scene.add(this.ambient);
this.renderer.scene.add(this.directional);
}
update(delta: number): void {
this.handlerCleanUp = this.initializeEvents();
}
cleanUp(): void {
this.renderer.scene.remove(this.ambient);
this.renderer.scene.remove(this.directional);
this.handlerCleanUp?.call(this);
}
private initializeEvents() {
const setEnvironmentEvent = (event: EnvironmentEvent) => {
if (event.sunColor) this.directional.color = new Color(event.sunColor);
if (event.sunPosition) this.directional.position.copy(event.sunPosition);
if (event.sunStrength) this.directional.intensity = event.sunStrength;
if (event.ambientColor)
this.ambient.color = new Color(event.ambientColor);
if (event.ambientStrength) this.ambient.intensity = event.ambientStrength;
if (event.clearColor)
this.renderer.renderer.setClearColor(event.clearColor);
};
this.events.addListener('setEnvironment', setEnvironmentEvent);
return () => {
this.events.removeEventListener('setEnvironment', setEnvironmentEvent);
};
}
}

View File

@ -0,0 +1,3 @@
export * from './environment';
export * from './viewport';
export * from './mouse';

View File

@ -1,12 +1,13 @@
import { EngineComponent, EventEmitter, Renderer } from '@freeblox/engine';
import { EditorEvents, SelectEvent } from '../types/events';
import { Object3D, Raycaster, Vector2 } from 'three';
import { EngineComponent } from '../types/engine-component';
import { Renderer } from '../core/renderer';
import { EngineEvents } from '../types/events';
import { EventEmitter } from '../utils/events';
type MouseMap = [boolean, boolean, boolean];
type EventMap = [string, Function];
export class EditorMouse extends EngineComponent {
private helpers!: Object3D;
export class MouseComponent extends EngineComponent {
private world!: Object3D;
private mouseButtons: MouseMap = [false, false, false];
@ -22,7 +23,7 @@ export class EditorMouse extends EngineComponent {
constructor(
protected renderer: Renderer,
protected events: EventEmitter<EditorEvents>
protected events: EventEmitter<EngineEvents>
) {
super(renderer, events);
}
@ -32,7 +33,6 @@ export class EditorMouse extends EngineComponent {
}
initialize(): void {
this.helpers = this.renderer.scene.getObjectByName('_helpers')!;
this.world = this.renderer.scene.getObjectByName('_world')!;
const mouseDown = (ev: MouseEvent) => {
@ -57,6 +57,9 @@ export class EditorMouse extends EngineComponent {
positionGL: this.mousePositionGL,
button: ev.button,
target: object,
shift: ev.shiftKey,
control: ev.ctrlKey,
alt: ev.altKey,
});
};
@ -103,11 +106,4 @@ export class EditorMouse extends EngineComponent {
this.canvas.removeEventListener(event, handler as EventListener);
}
}
// private realMousePos(vec: Vector2, x: number, y: number) {
// const rect = this.renderer.renderer.domElement.getBoundingClientRect();
// const scaleX = this.renderer.renderer.domElement.width / rect.width;
// const scaleY = this.renderer.renderer.domElement.height / rect.height;
// vec.set((x - rect.left) * scaleX, (y - rect.top) * scaleY);
// }
}

View File

@ -1,11 +1,13 @@
import { EngineComponent, EventEmitter, Renderer } from '@freeblox/engine';
import { Object3D } from 'three';
import { EditorEvents } from '../types/events';
import { EngineEvents } from '../types/events';
import { EngineComponent } from '../types/engine-component';
import { Renderer } from '../core/renderer';
import { EventEmitter } from '../utils/events';
export class EditorViewport extends EngineComponent {
export class ViewportComponent extends EngineComponent {
constructor(
protected render: Renderer,
protected events: EventEmitter<EditorEvents>
protected events: EventEmitter<EngineEvents>
) {
super(render, events);
}

View File

@ -0,0 +1,46 @@
import {
BufferGeometry,
Color,
ColorRepresentation,
Material,
Mesh,
MeshPhongMaterial,
} from 'three';
import { GameObject3D } from '../types/game-object';
import { Property } from '../types/property';
import { gameObjectFactory } from './factory';
export class Brick extends GameObject3D {
public objectType = Brick.name;
protected material = new MeshPhongMaterial();
protected mesh: Mesh = new Mesh(this.geometry, this.material);
constructor(
protected geometry: BufferGeometry = gameObjectFactory.boxGeometry
) {
super();
this.editorProperties.color = new Property('color', Color, true, []);
this.editorProperties.transparency = new Property(
'transparency',
Number,
true,
[]
);
this.add(this.mesh);
}
get color() {
return this.material.color;
}
set color(color: ColorRepresentation) {
this.material.color = new Color(color);
}
get transparency() {
return 1 - this.material.opacity;
}
set transparency(value: number) {
this.material.transparent = value != 0;
this.material.opacity = 1 - value;
}
}

View File

@ -0,0 +1,13 @@
import { Mesh, Color } from 'three';
import { Property } from '../types/property';
import { Brick } from './brick.object';
import { gameObjectFactory } from './factory';
export class Cylinder extends Brick {
public objectType = Cylinder.name;
protected mesh = new Mesh(this.geometry, this.material);
constructor() {
super(gameObjectFactory.cylinderGeometry);
}
}

View File

@ -0,0 +1,10 @@
import { BoxGeometry, CylinderGeometry, SphereGeometry } from 'three';
class GameObjectFactory {
public boxGeometry = new BoxGeometry();
public sphereGeometry = new SphereGeometry(0.5);
public cylinderGeometry = new CylinderGeometry(0.5, 0.5);
}
export const gameObjectFactory = new GameObjectFactory();
Object.freeze(gameObjectFactory);

View File

@ -0,0 +1,3 @@
export * from './brick.object';
export * from './cylinder.object';
export * from './sphere.object';

View File

@ -0,0 +1,13 @@
import { Mesh, Color } from 'three';
import { Property } from '../types/property';
import { Brick } from './brick.object';
import { gameObjectFactory } from './factory';
export class Sphere extends Brick {
public objectType = Sphere.name;
protected mesh = new Mesh(this.geometry, this.material);
constructor() {
super(gameObjectFactory.sphereGeometry);
}
}

View File

@ -1,3 +1,5 @@
export * from './core';
export * from './utils';
export * from './types';
export * from './components';
export * from './gameobjects';

View File

@ -1,3 +1,44 @@
import {
Vector2,
Intersection,
Object3D,
ColorRepresentation,
Vector3,
} from 'three';
export interface MousePositionEvent {
position: Vector2;
positionGL: Vector2;
}
export interface MouseMoveEvent extends MousePositionEvent {
offset: Vector2;
offsetGL: Vector2;
helper?: boolean;
}
export interface MouseButtonEvent extends MousePositionEvent {
button: number;
helper?: boolean;
target?: Intersection<Object3D>;
shift?: boolean;
control?: boolean;
alt?: boolean;
}
export interface EnvironmentEvent {
sunColor?: ColorRepresentation;
sunPosition?: Vector3;
sunStrength?: number;
ambientColor?: ColorRepresentation;
ambientStrength?: number;
clearColor?: ColorRepresentation;
}
export type EngineEvents = {
error: (error: Error) => void;
mouseDown: (event: MouseButtonEvent) => void;
mouseUp: (event: MouseButtonEvent) => void;
mouseMove: (event: MouseMoveEvent) => void;
setEnvironment: (event: EnvironmentEvent) => void;
};

View File

@ -0,0 +1,19 @@
import { Euler, Object3D, Vector3 } from 'three';
import { Property } from './property';
export class GameObject extends Object3D {
public objectType = 'GameObject';
public editorProperties: { [x: string]: Property } = {
name: new Property('name', String, true, []),
};
}
export class GameObject3D extends GameObject {
public objectType = 'GameObject3D';
public editorProperties: { [x: string]: Property } = {
name: new Property('name', String, true, []),
position: new Property('position', Vector3, true, []),
scale: new Property('scale', Vector3, true, []),
rotation: new Property('rotation', Euler, true, []),
};
}

View File

@ -1,3 +1,4 @@
export * from './game-runner';
export * from './events';
export * from './engine-component';
export * from './game-object';

View File

@ -0,0 +1,8 @@
export class Property {
constructor(
public name: string,
public type: any,
public exposed = true,
public validators: Function[] = []
) {}
}