Skip to content

Commit

Permalink
feat: add ortho control for camera (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
singlecoder authored Jun 8, 2021
1 parent 06762cd commit c4c72e4
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 0 deletions.
127 changes: 127 additions & 0 deletions packages/controls/src/OrthoControl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Camera, Entity, Logger, Script, Vector2, Vector3 } from "oasis-engine";

/**
* The camera's 2D controller, can zoom and pan.
*/
export class OrthoControl extends Script {
cameraEntity: Entity;
camera: Camera;

private _zoomSpeed: number = 1.0;
private _zoomScale: number = 1.0;
private _zoomScaleUnit: number = 25.0;
private _zoomMinSize: number = 0.0;
private _zoomMaxSize: number = Infinity;
private _isPanStart: boolean = false;
private _panStartPos: Vector3 = new Vector3();
private _panStart: Vector2 = new Vector2();
private _panEnd: Vector2 = new Vector2();
private _panDelta: Vector2 = new Vector2();

/**
* The zoom speed.
*/
get zoomSpeed(): number {
return this._zoomSpeed;
}

set zoomSpeed(value: number) {
this._zoomSpeed = value;
}

constructor(entity: Entity) {
super(entity);

this.cameraEntity = entity;
this.camera = entity.getComponent(Camera);
}

onUpdate(dt: number): void {
if (this._zoomScale !== 1) {
const { camera } = this;
const sizeDiff = this._zoomScaleUnit * (this._zoomScale - 1);
const size = camera.orthographicSize + sizeDiff;
camera.orthographicSize = Math.max(this._zoomMinSize, Math.min(this._zoomMaxSize, size));
this._zoomScale = 1;
}

if (this._isPanStart) {
const { _panStart: panStart, _panEnd: panEnd } = this;
const panDelta = this._panDelta;
Vector2.subtract(panEnd, panStart, panDelta);
if (panDelta.x === 0 && panDelta.y === 0) {
return ;
}
this._handlePan();
panEnd.cloneTo(panStart);
}
}

/**
* Zoom in.
*/
zoomIn(): void {
this._zoomScale *= this._getZoomScale();
}

/**
* Zoom out.
*/
zoomOut(): void {
this._zoomScale /= this._getZoomScale();
}

/**
* Start pan.
* @param x - The x-axis coordinate (unit: pixel)
* @param y - The y-axis coordinate (unit: pixel)
*/
panStart(x: number, y: number): void {
if (!this.enabled) return;

this.cameraEntity.transform.position.cloneTo(this._panStartPos);
this._panStart.setValue(x, y);
this._panEnd.setValue(x, y);
this._isPanStart = true;
}

/**
* Panning.
* @param x - The x-axis coordinate (unit: pixel)
* @param y - The y-axis coordinate (unit: pixel)
*
* @remarks Make sure to call panStart before calling panMove.
*/
panMove(x: number, y: number): void {
if (!this.enabled) return;
if (!this._isPanStart) {
Logger.warn("Make sure to call panStart before calling panMove");
}
this._panEnd.setValue(x, y);
}

/**
* End pan.
*/
panEnd(): void {
if (!this.enabled) return;
this._isPanStart = false;
}

private _getZoomScale(): number {
return Math.pow(0.95, this.zoomSpeed);
}

private _handlePan(): void {
const { width, height } = this.engine.canvas;
const { x, y } = this._panDelta;
const { camera } = this;
const doubleOrthographicSize = camera.orthographicSize * 4;
const width3D = doubleOrthographicSize * camera.aspectRatio;
const height3D = doubleOrthographicSize;
const pos = this._panStartPos;
pos.x -= (x * width3D) / width;
pos.y += (y * height3D) / height;
this.cameraEntity.transform.position = pos;
}
}
1 change: 1 addition & 0 deletions packages/controls/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { FreeControl } from "./FreeControl";
export { OrbitControl } from "./OrbitControl";
export { OrthoControl } from "./OrthoControl";
39 changes: 39 additions & 0 deletions packages/controls/tests/OrthoControl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Camera, Entity, WebGLEngine } from "oasis-engine";
import { OrthoControl } from "../src/OrthoControl";

const canvasDOM = document.createElement("canvas");
canvasDOM.width = 1024;
canvasDOM.height = 1024;

describe(" Ortho Control", () => {
let entity: Entity;
let camera: Camera;
let cameraControl: OrthoControl;

beforeAll(() => {
const engine = new WebGLEngine(canvasDOM);
entity = engine.sceneManager.activeScene.createRootEntity();
camera = entity.addComponent(Camera);
cameraControl = entity.addComponent(OrthoControl);
});

it("test zoom", () => {
cameraControl.zoomIn();
cameraControl.onUpdate(0);
expect(camera.orthographicSize).toEqual(8.749999999999998);
cameraControl.zoomOut();
cameraControl.onUpdate(0);
expect(camera.orthographicSize).toEqual(10.065789473684207);
});

it("test pan", () => {
cameraControl.panStart(0, 0);
cameraControl.panMove(2, 0);
cameraControl.onUpdate(0);
cameraControl.panEnd();

const pos = entity.transform.position;
expect(pos.x).toEqual(-0.07863898026315787);
expect(pos.y).toEqual(0);
});
});

0 comments on commit c4c72e4

Please sign in to comment.