Skip to content

Commit

Permalink
Basic camera transforms
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaswp committed Sep 2, 2022
1 parent bf48379 commit 5675153
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 116 deletions.
202 changes: 89 additions & 113 deletions src/Camera.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { OverrideRegistry, Blocks, Snap } from "sef";
import { HandMorph, Point, SpriteMorph, StageMorph } from "sef/src/snap/Snap";
import { HandMorph, IDE_Morph, Point, SpriteMorph, StageMorph } from "sef/src/snap/Snap";

interface CameraTarget {
scale: number;
center: () => Point;
getScale?: () => number;
isUser?: boolean;
sprite: SpriteMorph | StageMorph;
transform: Transform;
}

// class StageTarget implements CameraTarget {
Expand All @@ -28,18 +26,19 @@ const ZERO = new Point(0, 0); // TODO: Why doesn't ZERO exist?

export class Camera {

target : CameraTarget;
center: Point;
scale: number;
target: CameraTarget;
transform: Transform;
snap: number;
isPanning = false;
panMouseStart: Point;
panCameraStart: Point;

constructor() {
this.target = Snap.stage;
this.center = ZERO;
this.scale = 1;
this.target = {
sprite: Snap.stage,
transform: new Transform(),
};
this.transform = new Transform();
this.snap = 100;
}

Expand All @@ -51,32 +50,28 @@ export class Camera {
static isHoldingCamera(sprite: SpriteMorph) {
let camera = this.getCamera();
if (!camera) return false;
return camera.target === sprite;
return camera.target.sprite === sprite;
}

isUserControlling() : boolean {
return this.target && this.target.isUser;
return this.target && !this.target.sprite;
}

startUserControl() {
this.target = {
scale: this.scale,
center: () => this.center.copy(),
isUser: true,
sprite: null,
transform: this.transform,
}
}

previewCenter() : Point {
if (!this.target || this.target instanceof StageMorph) {
return ZERO;
} else if (this.isUserControlling()) {
previewPosition() : Point {
if (this.isUserControlling()) {
this.updateUserCamera()
}
return this.target.center();
return this.target.transform.position;
}

updateUserCamera() {
let target = this.target;
if (!this.isPanning) return;
if (!Snap.world || !Snap.IDE) return;
let hand = Snap.world.hand as HandMorph;
Expand All @@ -90,28 +85,32 @@ export class Camera {
}
let mousePos = hand.position();
let offset = this.panMouseStart.subtract(mousePos)
.multiplyBy(1 / target.scale / stage.scale);
.multiplyBy(1 / this.transform.scale / stage.scale);
offset.y = -offset.y;
target.center = this.panCameraStart.add(offset);
this.transform.position = this.panCameraStart.add(offset);;
}

previewScale() {
if (!this.target || this.target instanceof StageMorph) {
return 1
// TODO: Why are these different?
} else if (this.isUserControlling()) {
return this.target.scale;
return this.target.transform.scale;
}
return 100 / this.target.getScale();
return 100 / this.target.transform.scale;
}

update() {
let rate = 0.05 + 0.95 * Math.pow((this.snap) / 100, 4);
this.center = this.center.lerp(this.previewCenter(), rate, 1);
this.scale = lerp(this.scale, this.previewScale(), rate, 0.01);
this.transform.position = this.transform.position.lerp(this.previewPosition(), rate, 1);
this.transform.scale = lerp(this.transform.scale, this.previewScale(), rate, 0.01);
}

setTarget(sprite) {
this.target = sprite;
setTarget(sprite: SpriteMorph | StageMorph) {
this.target = {
sprite: sprite,
transform: sprite?.transform || new Transform(),
};
}
}

Expand Down Expand Up @@ -155,20 +154,31 @@ class Transform {
return pos.add(this.position);
}

// TODO
getGlobalRotation() {
return this.rotation;
let camera = Camera.getCamera();
let rotation = this.rotation;
// if (camera) rotation += rotation;
return rotation;
}

getGlobalScale() {
return this.scale;
let camera = Camera.getCamera();
let scale = this.scale;
if (camera) scale *= camera.transform.scale;
return scale;
}

getGlobalPosition(sprite: SpriteMorph) {
let stage = sprite.parentThatIsA(StageMorph);
if (!stage) return this.position.copy();
let newX = stage.center().x + this.position.x * stage.scale;
let newY = stage.center().y - this.position.y * stage.scale;
let camera = Camera.getCamera();
let position = this.position;
if (camera) {
position = position.subtract(camera.transform.position);
position = position.multiplyBy(camera.transform.scale);
}
let newX = stage.center().x + position.x * stage.scale;
let newY = stage.center().y - position.y * stage.scale;
if (sprite.costume) {
return new Point(newX, newY).subtract(sprite.rotationOffset);
} else {
Expand All @@ -177,9 +187,10 @@ class Transform {
}
}

SpriteMorph.prototype.updateGlobalTransform = function(justMe, silently) {
SpriteMorph.prototype.updateGlobalTransform = function(justMe) {
// let stage = this.getStage();
// if (!stage) return;
if (this.isUpdatingGlobalTransform) return;
this.isUpdatingGlobalTransform = true;
let heading = this.transform.getGlobalRotation();
if (heading != this.heading) this.setGlobalHeading(heading, true);
Expand All @@ -200,36 +211,7 @@ SpriteMorph.prototype.updateStageTransform = function() {

OverrideRegistry.after(SpriteMorph, 'init', function() {
this.transform = new Transform().set(this);
this.stageMode = false;
this.globalHeading = this.header;
this.globalScale = this.scale;
this.dirtyTransform = false;
// Object.defineProperty(this, 'heading', {
// get() {
// if (this.stageMode) return this.transform.rotation;
// return this.globalHeading;
// },
// set(value) {
// if (this.stageMode) {
// this.transform.rotation = value;
// this.dirtyTransform = true;
// }
// else this.globalHeading = value;
// }
// });
// Object.defineProperty(this, 'scale', {
// get() {
// if (this.stageMode) return this.transform.scale;
// return this.globalScale;
// },
// set(value) {
// if (this.stageMode) {
// this.transform.scale = value;
// this.dirtyTransform = true;
// }
// else this.globalScale = value;
// }
// });
});

OverrideRegistry.after(SpriteMorph, 'setPosition', function() {
Expand Down Expand Up @@ -257,6 +239,8 @@ OverrideRegistry.extend(SpriteMorph, 'gotoXY', function(base, x, y, justMe, noSh
this.shadowAttribute('y position');
}
this.transform.position = new Point(x, y);
// Update transform directly so we can draw each movement separately
this.dirtyTransform = true;
this.updateGlobalTransform(justMe);
});

Expand Down Expand Up @@ -290,7 +274,7 @@ SpriteMorph.prototype.setHeading = function (degrees, noShadow) {
if (!noShadow) {
this.shadowAttribute('direction');
}
this.updateGlobalTransform();
this.dirtyTransform = true;
};

SpriteMorph.prototype.turn = function (degrees) {
Expand All @@ -315,7 +299,7 @@ SpriteMorph.prototype.setScale = function (scale, noShadow) {
if (!noShadow) {
this.shadowAttribute('size');
}
this.updateGlobalTransform();
this.dirtyTransform = true;
};

SpriteMorph.prototype.getScale = function () {
Expand All @@ -326,9 +310,6 @@ SpriteMorph.prototype.getScale = function () {
return this.transform.scale * 100;
};




// TODO: Needed?
// StageMorph.prototype.cameraScale = function() {
// let camera = Camera.getCamera();
Expand All @@ -342,52 +323,47 @@ SpriteMorph.prototype.getScale = function () {
// return camera.center;
// }

// StageMorph.prototype.updateSpriteForCamera = function(force: boolean) {
// let camera = Camera.getCamera();
// if (!camera) return;
// let scale = camera.scale, center = camera.center,
// newScale = camera.previewScale(), newCenter = camera.previewCenter();
// if (force) {
// if (center != null && scale == newScale && newCenter.eq(center)) return;
// }
// camera.update();
// this.children.forEach(child => {
// if (child instanceof SpriteMorph) {
// child.updateGlobalTransform();
// child.setScale(child.getScale());
// }
// });
// }
StageMorph.prototype.updateSpriteForCamera = function(force: boolean) {
let camera = Camera.getCamera();
if (!camera) return;
let scale = camera.transform.scale, center = camera.transform.position,
newScale = camera.previewScale(), newCenter = camera.previewPosition();
let forceDirty = force || scale != newScale || !newCenter.eq(center);
camera.update();
this.children.forEach(child => {
if (child instanceof SpriteMorph) {
if (forceDirty) child.dirtyTransform = true;
child.updateGlobalTransform();
}
});
}

// OverrideRegistry.extend(StageMorph, 'init', function(base, globals) {
// base.call(this, globals);
// this.threads.camera = new Camera();
// });
OverrideRegistry.after(StageMorph, 'init', function(globals) {
this.threads.camera = new Camera();
});

// OverrideRegistry.extend(StageMorph, 'mouseScroll', function(base, y) {
// base.call(this, y);
// let camera = Camera.getCamera();
// if (!camera || !camera.isUserControlling()) return;
// if (camera.isPanning) return;
// camera.target.scale *= Math.pow(1.1, y);
// });
OverrideRegistry.after(StageMorph, 'mouseScroll', function(y) {
let camera = Camera.getCamera();
if (!camera || !camera.isUserControlling()) return;
if (camera.isPanning) return;
camera.transform.scale *= Math.pow(1.1, y);
});

// OverrideRegistry.extend(StageMorph, 'mouseDownLeft', function(base, pos) {
// let camera = Camera.getCamera();
// if (!camera || !camera.isUserControlling()) {
// base.call(this, pos);
// return;
// }
// let user = camera.target;
// camera.isPanning = true;
// camera.panMouseStart = pos.copy();
// camera.panCameraStart = user.center().copy();
// });

// OverrideRegistry.extend(StageMorph, 'stepFrame', function(base) {
// base.call(this);
// this.updateSpriteForCamera();
// });
OverrideRegistry.extend(StageMorph, 'mouseDownLeft', function(base, pos) {
let camera = Camera.getCamera();
if (!camera || !camera.isUserControlling()) {
base.call(this, pos);
return;
}
let user = camera.target;
camera.isPanning = true;
camera.panMouseStart = pos.copy();
camera.panCameraStart = camera.transform.position;
}, false);

OverrideRegistry.after(StageMorph, 'stepFrame', function() {
this.updateSpriteForCamera();
});


const Block = Blocks.Block;
Expand Down
5 changes: 2 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Blocks, Events, Extension, Snap } from 'sef';
import { Color } from 'sef/src/snap/Snap';
import { Levels } from './level';
import { Camera } from './Camera';
import { addCameraBlocks, Camera } from './Camera';

export class SnapGames extends Extension {
addBlocks(blocks: Blocks.BlockFactory): void {
Levels.addBlocks(blocks);
addCameraBlocks(blocks);
}

init() {
Expand All @@ -15,8 +16,6 @@ export class SnapGames extends Extension {
this.events.addListener(new Events.Block.SnappedListener(args => {
console.log(args.id);
}));

new Camera();
}
}

Expand Down

0 comments on commit 5675153

Please sign in to comment.