Skip to content

Commit

Permalink
feat: implement auto camera animation
Browse files Browse the repository at this point in the history
  • Loading branch information
Neosoulink committed Jan 15, 2024
1 parent a036c8d commit a13ab93
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 264 deletions.
1 change: 1 addition & 0 deletions src/common/experiences/navigation.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Spherical, Vector3 } from "three";

export interface NavigationView {
enabled?: boolean;
controls?: boolean;
center?: Vector3;
spherical?: {
smoothed: Spherical;
Expand Down
2 changes: 1 addition & 1 deletion src/config/common.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export abstract class CommonConfig {
"M0,0 C0.001,0.001 0.002,0.003 0.003,0.004 0.142,0.482 0.284,0.75 0.338,0.836 0.388,0.924 0.504,1 1,1";
static readonly DEBUG = (() => {
try {
return useRuntimeConfig().public.env === "development";
return useRuntimeConfig().public.env !== "development";
} catch (_) {
return false;
}
Expand Down
177 changes: 177 additions & 0 deletions src/experiences/home/camera-aninmation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { CatmullRomCurve3, Vector3 } from "three";
import { gsap } from "gsap";

// EXPERIENCES
import { HomeExperience } from ".";

// BLUEPRINTS
import { ExperienceBasedBlueprint } from "~/blueprints/experiences/experience-based.blueprint";
import { Config } from "~/config";

export const defaultCameraPath = new CatmullRomCurve3([
new Vector3(0, 5.5, 21),
new Vector3(12, 10, 12),
new Vector3(21, 5.5, 0),
new Vector3(12, 3.7, 12),
new Vector3(0, 5.5, 21),
]);
export const defaultCameraTarget = new Vector3(0, 2, 0);

export class CameraAnimation extends ExperienceBasedBlueprint {
protected readonly _experience = new HomeExperience();

private readonly _appSizes = this._experience.app.sizes;
private readonly _appCamera = this._experience.app.camera;
private readonly _camera = this._experience.camera;
private readonly _navigation = this._experience.navigation;

public enabled = false;
public cameraPath = defaultCameraPath;
public cameraTarget = defaultCameraTarget;
public progress = {
current: 0,
target: 0,
ease: 0.1,
};
public positionInCurve = new Vector3();
public reversed = false;
public timeline = gsap.timeline();
public mouseDowned = false;
public cursorCoordinate = { x: 0, y: 0 };
public normalizedCursorCoordinate = { x: 0, y: 0 };

private _onWheel?: (e: WheelEvent) => unknown;
private _onMousemove?: (e: MouseEvent) => unknown;
private _onMousedown?: (e: MouseEvent) => unknown;
private _onMouseUp?: (e: MouseEvent) => unknown;

private _wheelEvent(e: WheelEvent) {
if (!this.enabled) return;

if (e.deltaY < 0) {
this.progress.target += 0.05;
this.reversed = false;

return;
}

this.progress.target -= 0.05;
this.reversed = true;
}

private _mouseMoveEvent(e: MouseEvent) {
if (this.enabled && this.mouseDowned) {
if (e.clientX < this.cursorCoordinate.x) {
this.progress.target += 0.002;
this.reversed = false;
} else if (e.clientX > this.cursorCoordinate.x) {
this.progress.target -= 0.002;
this.reversed = true;
}
}

if (!this.enabled) {
this.normalizedCursorCoordinate.x =
e.clientX / this._appSizes?.width - 0.5;
this.normalizedCursorCoordinate.y =
e.clientY / this._appSizes?.height - 0.5;
}

this.cursorCoordinate = { x: e.clientX, y: e.clientY };
}

private _mouseDownEvent(e: MouseEvent) {
this.mouseDowned = true;
// if (this.focusedPosition && !this.mouseOverBubble)
// this._camera?.cameraZoomIn();
}

private _mouseUpEvent(e: MouseEvent) {
this.mouseDowned = false;
// if (this.focusedPosition && !this.mouseOverBubble)
// this._experience.camera?.cameraZoomOut();
}

public construct() {
this._onWheel = (e) => this._wheelEvent(e);
this._onMousemove = (e) => this._mouseMoveEvent(e);
this._onMousedown = (e) => this._mouseDownEvent(e);
this._onMouseUp = (e) => this._mouseUpEvent(e);

window.addEventListener("wheel", this._onWheel);
window.addEventListener("mousemove", this._onMousemove);
window.addEventListener("mousedown", this._onMousedown);
window.addEventListener("mouseup", this._onMouseUp);
}

public destruct() {
this._onWheel && window.removeEventListener("wheel", this._onWheel);
this._onMousemove &&
window.removeEventListener("mousemove", this._onMousemove);
this._onMousedown &&
window.removeEventListener("mousedown", this._onMousedown);
this._onMouseUp && window.removeEventListener("mouseup", this._onMouseUp);
}

public enable() {
if (this.enabled) return;
if (this._navigation?.view) this._navigation.view.controls = false;

const toPosition = this.cameraPath.getPointAt(
this.progress.current,
this.positionInCurve
);

this._navigation
?.updateCameraPosition(toPosition, this.cameraTarget)
.then(() => {
this.enabled = true;
});
}

public disable() {
if (!this.enabled) return;
this.enabled = false;

if (this.timeline.isActive()) this.timeline.progress(1);

this._navigation
?.updateCameraPosition(
this.positionInCurve.setY(2),
this.cameraTarget,
Config.GSAP_ANIMATION_DURATION * 0.5
)
.then(() => {
if (this._navigation?.view) this._navigation.view.controls = true;
});
}

public update(): void {
if (!this.enabled || this.timeline.isActive()) return;

this.progress.current = gsap.utils.interpolate(
this.progress.current,
this.progress.target,
this.progress.ease
);
this.progress.target =
this.progress.target + (this.reversed ? -0.0001 : 0.0001);

if (this.progress.target > 1) {
setTimeout(() => {
this.reversed = true;
}, 1000);
}

if (this.progress.target < 0) {
setTimeout(() => {
this.reversed = false;
}, 1000);
}
this.progress.target = gsap.utils.clamp(0, 1, this.progress.target);
this.progress.current = gsap.utils.clamp(0, 1, this.progress.current);
this.cameraPath.getPointAt(this.progress.current, this.positionInCurve);

this._navigation?.setPositionInSphere(this.positionInCurve);
}
}
4 changes: 0 additions & 4 deletions src/experiences/home/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import gsap from "gsap";

// EXPERIENCES
import { HomeExperience } from ".";
import { Navigation } from "./navigation";

// BLUEPRINTS
import { ExperienceBasedBlueprint } from "~/blueprints/experiences/experience-based.blueprint";
Expand Down Expand Up @@ -78,9 +77,6 @@ export class Camera extends ExperienceBasedBlueprint {
this._appDebug?.cameraHelper?.dispose();
}

if (this._experience.app.debug?.cameraControls)
this._experience.app.debug.cameraControls.enabled = false;

if (!(this._appCameraInstance instanceof PerspectiveCamera)) return;

this.correctAspect();
Expand Down
29 changes: 17 additions & 12 deletions src/experiences/home/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class Debug extends ExperienceBasedBlueprint {
protected readonly _appDebug = this._experience.app.debug;
protected readonly _appCamera = this._experience.app.camera;
protected readonly _camera = this._experience.camera;
protected readonly _cameraAnimation = this._experience.cameraAnimation;
protected readonly _worldCameraHelpers?: CameraHelper[] = [];

protected _gui?: GUI;
Expand All @@ -44,12 +45,16 @@ export class Debug extends ExperienceBasedBlueprint {
if (this._gui) this.destruct();

this._gui = this._experience.app.debug?.ui?.addFolder(HomeExperience.name);
if (this._appDebug?.cameraControls) {
this._appDebug.cameraControls.dispose();
this._appDebug.cameraControls = undefined;
}

if (!this._gui || !this._experience.world) return;

this.cameraLookAtPointIndicator = new Mesh(
new SphereGeometry(0.1, 12, 12),
new MeshBasicMaterial({ color: "#ff0040" }),
new MeshBasicMaterial({ color: "#ff0040" })
);
this.cameraLookAtPointIndicator.visible = false;
this._camera?.cameras.forEach((item, id) => {
Expand All @@ -67,17 +72,17 @@ export class Debug extends ExperienceBasedBlueprint {
!this.cameraLookAtPointIndicator.visible;
},
},
"fn",
"fn"
)
.name("Toggle LookAt indicator visibility");

this._cameraCurvePathLine = new Line(
new BufferGeometry().setFromPoints(
[...Array(3).keys()].map(() => new Vector3(0, 0, 0)),
[...Array(3).keys()].map(() => new Vector3(0, 0, 0))
),
new LineBasicMaterial({
color: 0xff0000,
}),
})
);

this._gui
Expand All @@ -98,27 +103,27 @@ export class Debug extends ExperienceBasedBlueprint {
: this._experience.camera?.cameraZoomOut();
},
},
"fn",
"fn"
)
.name("Toggle camera zoom");

this._gui
.add(
{
fn: () => {
const WorldManager = this._experience.world?.manager;
if (WorldManager)
WorldManager.autoCameraAnimation =
!WorldManager.autoCameraAnimation;
if (this._cameraAnimation?.enabled === true)
this._cameraAnimation.disable();
else if (this._cameraAnimation?.enabled === false)
this._cameraAnimation.enable();
},
},
"fn",
"fn"
)
.name("Toggle auto camera animation");

this._experience.app.scene.add(
this._cameraCurvePathLine,
this.cameraLookAtPointIndicator,
this.cameraLookAtPointIndicator
);
}

Expand Down Expand Up @@ -156,7 +161,7 @@ export class Debug extends ExperienceBasedBlueprint {
this._appCamera?.instance &&
this._experience.camera &&
this.cameraLookAtPointIndicator?.position.copy(
this._experience.camera.lookAtPosition,
this._experience.camera.lookAtPosition
);
}
}
11 changes: 10 additions & 1 deletion src/experiences/home/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { ErrorFactory } from "~/errors";

// MODELS
import type { ExperienceConstructorProps } from "~/common/experiences/experience.model";
import { CameraAnimation } from "./camera-aninmation";
import { Vector3 } from "three";

export class HomeExperience extends ExperienceBlueprint {
public ui?: UI;
Expand All @@ -32,6 +34,7 @@ export class HomeExperience extends ExperienceBlueprint {
public composer?: Composer;
public camera?: Camera;
public world?: World;
public cameraAnimation?: CameraAnimation;
public navigation?: Navigation;
public debug?: Debug;

Expand All @@ -41,7 +44,7 @@ export class HomeExperience extends ExperienceBlueprint {
HomeExperience._self ?? {
..._,
debug: Config.DEBUG,
},
}
);
if (HomeExperience._self) return HomeExperience._self;
HomeExperience._self = this;
Expand All @@ -54,6 +57,7 @@ export class HomeExperience extends ExperienceBlueprint {
this.camera = new Camera();
this.world = new World();
this.navigation = new Navigation();
this.cameraAnimation = new CameraAnimation();
this.debug = new Debug();
} catch (_err) {
throw new ErrorFactory(_err);
Expand All @@ -73,6 +77,7 @@ export class HomeExperience extends ExperienceBlueprint {
this.camera?.destruct();
this.world?.destruct();
this.navigation?.destruct();
this.cameraAnimation?.destruct();
this.debug?.destruct();
this.app.destroy();

Expand All @@ -94,6 +99,7 @@ export class HomeExperience extends ExperienceBlueprint {
this.renderer?.construct();
this.world?.construct();
this.navigation?.construct();
this.cameraAnimation?.construct();
this.debug?.construct();
this.app?.setUpdateCallback(HomeExperience.name, () => this.update());
this._onConstruct?.();
Expand All @@ -102,6 +108,8 @@ export class HomeExperience extends ExperienceBlueprint {
}
});
this.loader?.construct();

this.camera?.setCameraLookAt(new Vector3(0, 2, 0));
} catch (_) {
throw new ErrorFactory(_);
}
Expand All @@ -111,6 +119,7 @@ export class HomeExperience extends ExperienceBlueprint {
try {
this.world?.update();
this.camera?.update();
this.cameraAnimation?.update();
this.navigation?.update();
this.composer?.update();
this.debug?.update();
Expand Down
Loading

0 comments on commit a13ab93

Please sign in to comment.