diff --git a/src/blueprints/experiences/scene-component.blueprint.ts b/src/blueprints/experiences/scene-component.blueprint.ts index 38158be..2cc32e2 100644 --- a/src/blueprints/experiences/scene-component.blueprint.ts +++ b/src/blueprints/experiences/scene-component.blueprint.ts @@ -18,6 +18,7 @@ import type { Materials, ModelChildrenMaterials, } from "~/common/experiences/experience-world.model"; +import type { NavigationView } from "~/common/experiences/navigation.model"; // TODO: Link with the names of assets in the `app.loader` assets names export interface SceneBlueprintProps { @@ -44,6 +45,11 @@ export abstract class SceneComponentBlueprint extends ExperienceBasedBlueprint { protected _modelScene?: Group; protected _availableMaterials: Materials = {}; + public abstract readonly navigationLimits?: { + spherical: Exclude["limits"]; + target: Exclude["limits"]; + }; + constructor(_: SceneBlueprintProps) { super(); diff --git a/src/common/experiences/navigation.model.ts b/src/common/experiences/navigation.model.ts new file mode 100644 index 0000000..4219ac3 --- /dev/null +++ b/src/common/experiences/navigation.model.ts @@ -0,0 +1,49 @@ +import type { Spherical, Vector3 } from "three"; + +export interface NavigationView { + enabled?: boolean; + center?: Vector3; + spherical?: { + smoothed: Spherical; + smoothing: number; + limits: { + radius: { min: number; max: number }; + phi: { min: number; max: number }; + theta: { min: number; max: number }; + enabled: boolean; + enabledPhi: boolean; + enabledTheta: boolean; + }; + value: Spherical; + }; + target?: { + value: Vector3; + smoothed: Vector3; + smoothing: number; + limits: { + x: { min: number; max: number }; + y: { min: number; max: number }; + z: { min: number; max: number }; + enabled: boolean; + }; + }; + drag?: { + delta: { x: number; y: number }; + previous: { x: number; y: number }; + sensitivity: number; + alternative: boolean; + }; + zoom?: { sensitivity: number; delta: number }; + down?: (x: number, y: number) => unknown; + move?: (x: number, y: number) => unknown; + up?: () => unknown; + zooming?: (delta: number) => unknown; + onMouseDown?: (event: MouseEvent) => unknown; + onMouseUp?: (this: Window, ev: MouseEvent) => unknown; + onMouseMove?: (this: Window, ev: MouseEvent) => unknown; + onTouchStart?: (event: TouchEvent) => unknown; + onTouchEnd?: (event: TouchEvent) => unknown; + onTouchMove?: (event: TouchEvent) => unknown; + onContextMenu?: (event: MouseEvent) => unknown; + onWheel?: (event: Event) => unknown; +} diff --git a/src/experiences/home/camera.ts b/src/experiences/home/camera.ts index 3ba0315..10ba689 100644 --- a/src/experiences/home/camera.ts +++ b/src/experiences/home/camera.ts @@ -71,6 +71,10 @@ export class Camera extends ExperienceBasedBlueprint { return this._timeline; } + public get currentCamera() { + return this.cameras[this.currentCameraIndex]; + } + public construct() { if (!Config.DEBUG && this._appDebug?.cameraHelper) { this._experience.app.scene.remove(this._appDebug?.cameraHelper); @@ -144,6 +148,8 @@ export class Camera extends ExperienceBasedBlueprint { currentCamera.near = nextCamera.near; currentCamera.far = nextCamera.far; + this.currentCamera.userData.lookAt = this._lookAtPosition; + this._appCameraInstance.copy(nextCamera); this._appCameraInstance.fov = this._prevCameraProps.fov; @@ -178,7 +184,13 @@ export class Camera extends ExperienceBasedBlueprint { */ public setCameraLookAt(v3 = new Vector3()) { const V3 = v3.clone(); - this._appCameraInstance?.lookAt(V3); + + if (this._appCameraInstance) { + this._appCameraInstance.lookAt(V3); + this._appCameraInstance.userData.lookAt = V3; + } + + this.currentCamera.userData.lookAt = V3; return (this._lookAtPosition = V3); } diff --git a/src/experiences/home/index.ts b/src/experiences/home/index.ts index 5281b34..f31514e 100644 --- a/src/experiences/home/index.ts +++ b/src/experiences/home/index.ts @@ -25,15 +25,15 @@ import { ErrorFactory } from "~/errors"; import type { ExperienceConstructorProps } from "~/common/experiences/experience.model"; export class HomeExperience extends ExperienceBlueprint { - ui?: UI; - router?: Router; - loader?: Loader; - renderer?: Renderer; - composer?: Composer; - camera?: Camera; - world?: World; - navigation?: Navigation; - debug?: Debug; + public ui?: UI; + public router?: Router; + public loader?: Loader; + public renderer?: Renderer; + public composer?: Composer; + public camera?: Camera; + public world?: World; + public navigation?: Navigation; + public debug?: Debug; constructor(_?: Omit) { try { diff --git a/src/experiences/home/navigation.ts b/src/experiences/home/navigation.ts index b504980..1307f1d 100644 --- a/src/experiences/home/navigation.ts +++ b/src/experiences/home/navigation.ts @@ -8,6 +8,9 @@ import { HomeExperience } from "./"; // BLUEPRINTS import { ExperienceBasedBlueprint } from "~/blueprints/experiences/experience-based.blueprint"; +// MODELS +import type { NavigationView } from "~/common/experiences/navigation.model"; + // STATIC import { ANIMATION_ENDED, ANIMATION_STARTED } from "~/static/event.static"; @@ -25,9 +28,16 @@ export class Navigation extends ExperienceBasedBlueprint { private readonly _targetElement = this._experience.app.renderer.instance.domElement; private readonly _appCamera = this._experience.app.camera; + private readonly _appSizes = this._experience.app.sizes; private readonly _camera = this._experience.camera; private readonly _time = this._experience.app.time; - private readonly _config: { [name: string]: any } = {}; + private readonly _config = { + pixelRatio: 0, + width: 0, + height: 0, + smallestSide: 0, + largestSide: 0, + }; private readonly _timeline = gsap.timeline({ onStart: () => { this._view.spherical?.limits && @@ -45,102 +55,62 @@ export class Navigation extends ExperienceBasedBlueprint { }, 100); }, }); + private readonly _viewLimits: { + spherical: Exclude["limits"]; + target: Exclude["limits"]; + } = { + spherical: { + radius: { min: 5, max: 20 }, + phi: { min: 0.01, max: Math.PI * 0.5 }, + theta: { min: 0, max: Math.PI * 0.5 }, + enabled: true, + enabledPhi: true, + enabledTheta: true, + }, + target: { + x: { min: -3, max: 3 }, + y: { min: 2, max: 6 }, + z: { min: -2.5, max: 4 }, + enabled: true, + }, + } as const; - private _view: { - enabled?: boolean; - center?: Vector3; - spherical?: { - smoothed?: Spherical; - smoothing?: number; - limits?: { - radius?: { min: number; max: number }; - phi?: { min: number; max: number }; - theta?: { min: number; max: number }; - enabled?: boolean; - enabledPhi?: boolean; - enabledTheta?: boolean; - }; - value?: Spherical; - }; - target?: { - value?: Vector3; - smoothed?: Vector3; - smoothing?: number; - limits?: { - x?: { min: number; max: number }; - y?: { min: number; max: number }; - z?: { min: number; max: number }; - enabled?: boolean; - }; - }; - drag?: { - delta?: { x: number; y: number }; - previous?: { x: number; y: number }; - sensitivity?: number; - alternative?: boolean; - }; - zoom?: { sensitivity?: number; delta?: number }; - down?: (x: number, y: number) => unknown; - move?: (x: number, y: number) => unknown; - up?: () => unknown; - zooming?: (delta: number) => unknown; - onMouseDown?: (event: MouseEvent) => unknown; - onMouseUp?: (this: Window, ev: MouseEvent) => unknown; - onMouseMove?: (this: Window, ev: MouseEvent) => unknown; - onTouchStart?: (event: TouchEvent) => unknown; - onTouchEnd?: (event: TouchEvent) => unknown; - onTouchMove?: (event: TouchEvent) => unknown; - onContextMenu?: (event: MouseEvent) => unknown; - onWheel?: (event: Event) => unknown; - } = {}; + private _view: NavigationView = {}; private _setView() { this._view.enabled = true; this._view.center = new Vector3(); - this._view.spherical = {}; - this._view.spherical.value = new Spherical(20, Math.PI * 0.5, 0); - this._view.spherical.smoothed = this._view.spherical.value.clone(); - this._view.spherical.smoothing = 0.005; - this._view.spherical.limits = {}; - this._view.spherical.limits.radius = { min: 5, max: 20 }; - this._view.spherical.limits.phi = { min: 0.01, max: Math.PI * 0.5 }; - this._view.spherical.limits.theta = { min: 0, max: Math.PI * 0.5 }; - this._view.spherical.limits.enabled = true; - this._view.spherical.limits.enabledPhi = true; - this._view.spherical.limits.enabledTheta = true; - - this._view.target = {}; - this._view.target.value = new Vector3(0, 2, 0); - this._view.target.smoothed = this._view.target.value.clone(); - this._view.target.smoothing = 0.005; - this._view.target.limits = {}; - this._view.target.limits.x = { min: -3, max: 3 }; - this._view.target.limits.y = { min: 2, max: 6 }; - this._view.target.limits.z = { min: -2.5, max: 4 }; - this._view.target.limits.enabled = true; - - this._view.drag = {}; - this._view.drag.delta = { x: 0, y: 0 }; - this._view.drag.previous = { x: 0, y: 0 }; - this._view.drag.sensitivity = 1; - this._view.drag.alternative = false; - - this._view.zoom = {}; - this._view.zoom.sensitivity = 0.01; - this._view.zoom.delta = 0; + this._view.spherical = { + value: new Spherical(20, Math.PI * 0.5, 0), + smoothed: new Spherical(20, Math.PI * 0.5, 0), + smoothing: 0.005, + limits: this._viewLimits.spherical, + }; + + this._view.target = { + value: new Vector3(0, 2, 0), + smoothed: new Vector3(0, 2, 0), + smoothing: 0.005, + limits: this._viewLimits.target, + }; + + this._view.drag = { + delta: { x: 0, y: 0 }, + previous: { x: 0, y: 0 }, + sensitivity: 1, + alternative: false, + }; + + this._view.zoom = { sensitivity: 0.01, delta: 0 }; - /** - * Methods - */ this._view.down = (_x, _y) => { if (!this._view.drag?.previous) return; this._view.drag.previous.x = _x; this._view.drag.previous.y = _y; }; - this._view.move = (_x, _y) => { if ( !this._view.enabled || @@ -155,9 +125,7 @@ export class Navigation extends ExperienceBasedBlueprint { this._view.drag.previous.x = _x; this._view.drag.previous.y = _y; }; - this._view.up = () => {}; - this._view.zooming = (_delta) => { if (typeof this._view.zoom?.delta !== "number") return; @@ -212,9 +180,6 @@ export class Navigation extends ExperienceBasedBlueprint { this._targetElement?.addEventListener("mousedown", this._view.onMouseDown); - /** - * Touch events - */ this._view.onTouchStart = (_event) => { _event.preventDefault(); @@ -256,9 +221,6 @@ export class Navigation extends ExperienceBasedBlueprint { window.addEventListener("touchstart", this._view.onTouchStart); - /** - * Context menu - */ this._view.onContextMenu = (_event) => { _event.preventDefault(); }; @@ -268,9 +230,6 @@ export class Navigation extends ExperienceBasedBlueprint { this._view.onContextMenu ); - /** - * Wheel - */ this._view.onWheel = (_event) => { _event.preventDefault(); @@ -288,13 +247,11 @@ export class Navigation extends ExperienceBasedBlueprint { } private _setConfig() { - // Pixel ratio - this._config.pixelRatio = Math.min(Math.max(window.devicePixelRatio, 1), 2); + this._config.pixelRatio = this._experience.app.sizes.pixelRatio; - // Width and height - const boundings = this._targetElement.getBoundingClientRect(); - this._config.width = boundings.width; - this._config.height = boundings.height || window.innerHeight; + const boundingClient = this._targetElement.getBoundingClientRect(); + this._config.width = boundingClient.width; + this._config.height = boundingClient.height || window.innerHeight; this._config.smallestSide = Math.min( this._config.width, this._config.height @@ -303,9 +260,6 @@ export class Navigation extends ExperienceBasedBlueprint { this._config.width, this._config.height ); - - // Debug - this._config.debug = this._config.width > 420; } public get timeline() { @@ -319,6 +273,8 @@ export class Navigation extends ExperienceBasedBlueprint { public construct() { this._setConfig(); this._setView(); + + this._appSizes.on("resize", () => this._setConfig()); } public destruct() {} @@ -405,7 +361,7 @@ export class Navigation extends ExperienceBasedBlueprint { onUpdate: gsap.Callback = () => {}, onComplete: gsap.Callback = () => {} ) { - if (!this._experience.app?.camera.instance) return this._timeline; + if (!this._appCamera.instance) return this._timeline; const lookAtA = this._view.target?.value?.clone(); const lookAtB = lookAt.clone(); @@ -447,6 +403,28 @@ export class Navigation extends ExperienceBasedBlueprint { }); } + /** + * Set navigation limits. use default config limits if not parameter was passed. + * + * @param _ Limits `spherical` and `target` + */ + public setLimits(_?: { + spherical?: Exclude["limits"]; + target?: Exclude["limits"]; + }) { + if (!_) { + this._view.spherical && + (this._view.spherical.limits = this._viewLimits.spherical); + this._view.target && (this._view.target.limits = this._viewLimits.target); + + return; + } + + if (_.spherical) + this._view.spherical && (this._view.spherical.limits = _.spherical); + if (_.target) this._view.target && (this._view.target.limits = _.target); + } + public update() { if ( !this._view.enabled || @@ -454,13 +432,8 @@ export class Navigation extends ExperienceBasedBlueprint { !this._view.zoom || !this._view.drag || !this._view.target || - !this._appCamera.instance || - !this._view.spherical.limits?.radius || - !this._view.spherical.limits.theta || - !this._view.spherical.limits.phi || - !this._view.spherical.value || - !this._view.spherical.smoothed || - !this._view.spherical.smoothing + !this._view.center || + !this._appCamera.instance ) return; @@ -482,85 +455,76 @@ export class Navigation extends ExperienceBasedBlueprint { this._view.target.value?.add(up); this._view.target.value?.add(right); } else { - if (this._view.drag.delta && this._view.drag.sensitivity) { - this._view.spherical.value.theta -= - (this._view.drag.delta.x * this._view.drag.sensitivity) / - this._config.smallestSide; - this._view.spherical.value.phi -= - (this._view.drag.delta.y * this._view.drag.sensitivity) / - this._config.smallestSide; - } + this._view.spherical.value.theta -= + (this._view.drag.delta.x * this._view.drag.sensitivity) / + this._config.smallestSide; + this._view.spherical.value.phi -= + (this._view.drag.delta.y * this._view.drag.sensitivity) / + this._config.smallestSide; } - // Apply limits - if (this._view.spherical.limits.enabled) - this._view.spherical.value.radius = Math.min( - Math.max( - this._view.spherical.value.radius, - this._view.spherical.limits.radius.min - ), - this._view.spherical.limits.radius.max - ); - - if ( - this._view.target.value && - this._view.target.limits?.x && - this._view.target.limits.y && - this._view.target.limits.z && - this._view.center && - this._view.target.limits.enabled - ) { - this._view.target.value.x = Math.min( - Math.max( - this._view.target.value.x, - this._view.target.limits.x.min + this._view.center.x - ), - this._view.target.limits.x.max + this._view.center.x - ); - - this._view.target.value.y = Math.min( - Math.max( - this._view.target.value.y, - this._view.target.limits.y.min + this._view.center.y - ), - this._view.target.limits.y.max + this._view.center.y - ); + if (!this.timeline.isActive()) { + // Apply limits + if (this._view.spherical.limits.enabled) + this._view.spherical.value.radius = Math.min( + Math.max( + this._view.spherical.value.radius, + this._view.spherical.limits.radius.min + ), + this._view.spherical.limits.radius.max + ); - this._view.target.value.z = Math.min( - Math.max( - this._view.target.value.z, - this._view.target.limits.z.min + this._view.center.z - ), - this._view.target.limits.z.max + this._view.center.z - ); - } + if (this._view.target.limits.enabled) { + this._view.target.value.x = Math.min( + Math.max( + this._view.target.value.x, + this._view.target.limits.x.min + this._view.center.x + ), + this._view.target.limits.x.max + this._view.center.x + ); - if (this._view.spherical.limits.enabled) { - if (this._view.spherical.limits.enabledPhi) - this._view.spherical.value.phi = Math.min( + this._view.target.value.y = Math.min( Math.max( - this._view.spherical.value.phi, - this._view.spherical.limits.phi.min + this._view.target.value.y, + this._view.target.limits.y.min + this._view.center.y ), - this._view.spherical.limits.phi.max + this._view.target.limits.y.max + this._view.center.y ); - if (this._view.spherical.limits.enabledTheta) - this._view.spherical.value.theta = Math.min( + this._view.target.value.z = Math.min( Math.max( - this._view.spherical.value.theta, - this._view.spherical.limits.theta.min + this._view.target.value.z, + this._view.target.limits.z.min + this._view.center.z ), - this._view.spherical.limits.theta.max + this._view.target.limits.z.max + this._view.center.z ); - } + } - if (this._view.drag.delta) { - this._view.drag.delta.x = 0; - this._view.drag.delta.y = 0; - this._view.zoom.delta = 0; + if (this._view.spherical.limits.enabled) { + if (this._view.spherical.limits.enabledPhi) + this._view.spherical.value.phi = Math.min( + Math.max( + this._view.spherical.value.phi, + this._view.spherical.limits.phi.min + ), + this._view.spherical.limits.phi.max + ); + + if (this._view.spherical.limits.enabledTheta) + this._view.spherical.value.theta = Math.min( + Math.max( + this._view.spherical.value.theta, + this._view.spherical.limits.theta.min + ), + this._view.spherical.limits.theta.max + ); + } } + this._view.drag.delta.x = 0; + this._view.drag.delta.y = 0; + this._view.zoom.delta = 0; + // Smoothing this._view.spherical.smoothed.radius += (this._view.spherical.value.radius - @@ -576,30 +540,24 @@ export class Navigation extends ExperienceBasedBlueprint { this._view.spherical.smoothing * this._time.delta; - if ( - this._view.target.smoothed && - this._view.target.value && - this._view.target.smoothing - ) { - this._view.target.smoothed.x += - (this._view.target.value.x - this._view.target.smoothed.x) * - this._view.target.smoothing * - this._time.delta; - this._view.target.smoothed.y += - (this._view.target.value.y - this._view.target.smoothed.y) * - this._view.target.smoothing * - this._time.delta; - this._view.target.smoothed.z += - (this._view.target.value.z - this._view.target.smoothed.z) * - this._view.target.smoothing * - this._time.delta; - - const viewPosition = new Vector3(); - viewPosition.setFromSpherical(this._view.spherical.smoothed); - viewPosition.add(this._view.target.smoothed); - - this._camera?.setCameraPosition(viewPosition); - this._camera?.setCameraLookAt(this._view.target.smoothed); - } + this._view.target.smoothed.x += + (this._view.target.value.x - this._view.target.smoothed.x) * + this._view.target.smoothing * + this._time.delta; + this._view.target.smoothed.y += + (this._view.target.value.y - this._view.target.smoothed.y) * + this._view.target.smoothing * + this._time.delta; + this._view.target.smoothed.z += + (this._view.target.value.z - this._view.target.smoothed.z) * + this._view.target.smoothing * + this._time.delta; + + const viewPosition = new Vector3(); + viewPosition.setFromSpherical(this._view.spherical.smoothed); + viewPosition.add(this._view.target.smoothed); + + this._camera?.setCameraPosition(viewPosition); + this._camera?.setCameraLookAt(this._view.target.smoothed); } } diff --git a/src/experiences/home/world/index.ts b/src/experiences/home/world/index.ts index 3241d30..09b1ff4 100644 --- a/src/experiences/home/world/index.ts +++ b/src/experiences/home/world/index.ts @@ -182,7 +182,7 @@ export class World extends ExperienceBasedBlueprint { this._projectedSceneConfig.position.set(0, HEIGHT * -2, 0); this._projectedSceneConfig.center.set( this._projectedSceneConfig.position.x, - this._projectedSceneConfig.position.y + 2, + this._projectedSceneConfig.position.y + 1.5, this._projectedSceneConfig.position.z ); this._projectedSceneConfig.cameraPath.points = [ diff --git a/src/experiences/home/world/manager.ts b/src/experiences/home/world/manager.ts index e2a2f98..e851166 100644 --- a/src/experiences/home/world/manager.ts +++ b/src/experiences/home/world/manager.ts @@ -43,24 +43,26 @@ export class WorldManager extends ExperienceBasedBlueprint { private readonly _composer = this._experience.composer; private readonly _renderer = this._experience.renderer; private readonly _timeline = gsap.timeline(); - private readonly _cameraTransitionShaderPass = new ShaderPass({ - uniforms: { - tDiffuse: { value: null }, - uStrength: { value: 0 }, - uDisplacementMap: { - value: this._appResources.items["rocksAlphaMap"], + + private _world: typeof this._experience.world; + private _config: { + prevSceneKey?: string; + cameraTransitionPass: ShaderPass; + glassEffectDefault: { duration: number; ease: gsap.EaseFunction }; + } = { + cameraTransitionPass: new ShaderPass({ + uniforms: { + tDiffuse: { value: null }, + uStrength: { value: 0 }, + uDisplacementMap: { + value: this._appResources.items["rocksAlphaMap"], + }, }, - }, - vertexShader: camTransitionVert, - fragmentShader: camTransitionFrag, - }); - - private _glassEffectOptions: gsap.TweenVars = { - duration: 0.3, - ease: Power0.easeIn, + vertexShader: camTransitionVert, + fragmentShader: camTransitionFrag, + }), + glassEffectDefault: { duration: 0.3, ease: Power0.easeIn }, }; - private _world: typeof this._experience.world; - private _prevSceneKey?: string; // TODO: Reorder properties public rayCaster = new Raycaster(); @@ -200,6 +202,8 @@ export class WorldManager extends ExperienceBasedBlueprint { SECONDARY_CAMERA.position ); SECONDARY_CAMERA.lookAt(this._world.projectedSceneConfig.center); + SECONDARY_CAMERA.userData.lookAt = + this._world.projectedSceneConfig.center; } this._setScene(); @@ -207,32 +211,35 @@ export class WorldManager extends ExperienceBasedBlueprint { /** Launch a screen glass effect. */ private _triggerGlassTransitionEffect() { - if (!this._cameraTransitionShaderPass.uniforms.uStrength || !this._composer) + if ( + !this._config.cameraTransitionPass.uniforms.uStrength || + !this._composer + ) return this._timeline; if (this._timeline.isActive()) this._timeline.progress(1); - this._cameraTransitionShaderPass.clear = true; - this._cameraTransitionShaderPass.uniforms.uStrength.value = 0; + this._config.cameraTransitionPass.clear = true; + this._config.cameraTransitionPass.uniforms.uStrength.value = 0; this._composer.addPass( - "_cameraTransitionShaderPass", - this._cameraTransitionShaderPass + "cameraTransitionPass", + this._config.cameraTransitionPass ); return this._timeline - .to(this._cameraTransitionShaderPass.material.uniforms.uStrength, { - ...this._glassEffectOptions, + .to(this._config.cameraTransitionPass.material.uniforms.uStrength, { + ...this._config.glassEffectDefault, value: 0.175, }) - .to(this._cameraTransitionShaderPass.material.uniforms.uStrength, { - ...this._glassEffectOptions, + .to(this._config.cameraTransitionPass.material.uniforms.uStrength, { + ...this._config.glassEffectDefault, value: 0, ease: Power0.easeOut, }) .add( () => - this._cameraTransitionShaderPass && - this._composer?.removePass("_cameraTransitionShaderPass"), + this._config.cameraTransitionPass && + this._composer?.removePass("cameraTransitionPass"), ">" ); } @@ -248,7 +255,7 @@ export class WorldManager extends ExperienceBasedBlueprint { if ( CURRENT_SCENE?.modelScene && nextSceneKey !== this._world?.mainSceneKey && - nextSceneKey !== this._prevSceneKey + nextSceneKey !== this._config.prevSceneKey ) { const PARAMS = { alphaTest: 0 }; CURRENT_SCENE.modelScene.renderOrder = 1; @@ -256,6 +263,10 @@ export class WorldManager extends ExperienceBasedBlueprint { this._timeline.to(PARAMS, { alphaTest: 1, duration: Config.GSAP_ANIMATION_DURATION, + onStart: () => { + if (!this._navigation?.timeline.isActive()) + this._navigation?.setLimits(CURRENT_SCENE.navigationLimits); + }, onUpdate: () => { this._supportedPageKeys.slice(1).forEach((supportedPageKey) => { const SCENE = this._world?.availablePageScenes[supportedPageKey]; @@ -317,7 +328,9 @@ export class WorldManager extends ExperienceBasedBlueprint { }) ); - if (this._camera.timeline.isActive()) this._camera.timeline.progress(1); + if (this._camera?.timeline.isActive()) this._camera.timeline.progress(1); + if (this._navigation?.timeline.isActive()) + this._navigation.timeline.progress(1); if (this._timeline.isActive()) this._timeline.progress(1); const CURRENT_SCENE = @@ -327,7 +340,7 @@ export class WorldManager extends ExperienceBasedBlueprint { new Vector3(); if ( - this._prevSceneKey && + this._config.prevSceneKey && (this._experience.router?.currentRouteKey === this._world.mainSceneKey || CURRENT_SCENE === undefined) ) { @@ -335,17 +348,14 @@ export class WorldManager extends ExperienceBasedBlueprint { if (!this._world?.mainSceneConfig) return; this._camera?.switchCamera(0); + this._navigation?.setLimits(CURRENT_SCENE.navigationLimits); this._navigation?.setViewCenter(new Vector3()); this._navigation?.setTargetPosition(SCREEN_POSITION); - this._navigation - ?.updateCameraPosition( - this._world.mainSceneConfig.cameraPath.getPoint(0), - this._world.mainSceneConfig.center - ) - .then(() => { - this._navigation?.disableFreeAzimuthRotation(); - }); - }, "-=" + this._glassEffectOptions.duration); + this._navigation?.updateCameraPosition( + this._world.mainSceneConfig.cameraPath.getPoint(0), + this._world.mainSceneConfig.center + ); + }, "-=" + this._config.glassEffectDefault.duration); } if ( @@ -367,25 +377,22 @@ export class WorldManager extends ExperienceBasedBlueprint { if (!this._world?.projectedSceneConfig.center) return; this._camera?.switchCamera(1); + this._navigation?.setLimits(CURRENT_SCENE.navigationLimits); this._navigation?.setViewCenter( this._world?.projectedSceneConfig.center ); this._navigation?.setTargetPosition( - this._world?.projectedSceneConfig.center + this._camera?.currentCamera.userData.lookAt ); this._navigation?.setPositionInSphere( - this._world?.projectedSceneConfig.cameraPath.getPoint(0) + this._camera?.currentCamera.position ); - }, "-=" + this._glassEffectOptions.duration); - }, "<87%") - .then(() => { - this._navigation?.enableFreeAzimuthRotation(); - }); + }, "-=" + this._config.glassEffectDefault.duration); + }, "<87%"); } this._changeProjectedScene(this._experience.router.currentRouteKey); - - this._prevSceneKey = this._experience.router?.currentRouteKey; + this._config.prevSceneKey = this._experience.router?.currentRouteKey; } public construct() { diff --git a/src/experiences/home/world/scene-1.component.ts b/src/experiences/home/world/scene-1.component.ts index 90f856b..931edc3 100644 --- a/src/experiences/home/world/scene-1.component.ts +++ b/src/experiences/home/world/scene-1.component.ts @@ -60,6 +60,22 @@ export class Scene1Component extends SceneComponentBlueprint { fixedComputerLed: "#50cdff", coffeeSteam: "#b7a08e", }; + public readonly navigationLimits = { + spherical: { + radius: { min: 5, max: 20 }, + phi: { min: 0.01, max: Math.PI * 0.5 }, + theta: { min: 0, max: Math.PI * 0.5 }, + enabled: true, + enabledPhi: true, + enabledTheta: true, + }, + target: { + x: { min: -3, max: 3 }, + y: { min: 2, max: 6 }, + z: { min: -2.5, max: 4 }, + enabled: true, + }, + }; public pcScreenWebglTexture = new WebGLRenderTarget(1024, 1024); public pcTopArticulation?: Object3D; diff --git a/src/experiences/home/world/scene-2.component.ts b/src/experiences/home/world/scene-2.component.ts index e16d645..4acb3c2 100644 --- a/src/experiences/home/world/scene-2.component.ts +++ b/src/experiences/home/world/scene-2.component.ts @@ -7,6 +7,23 @@ import { SceneComponentBlueprint } from "~/blueprints/experiences/scene-componen import type { Materials } from "~/common/experiences/experience-world.model"; export class Scene2Component extends SceneComponentBlueprint { + public readonly navigationLimits = { + spherical: { + radius: { min: 8, max: 15 }, + phi: { min: 0.01, max: Math.PI * 0.5 }, + theta: { min: 0, max: Math.PI * 0.5 }, + enabled: true, + enabledPhi: true, + enabledTheta: false, + }, + target: { + x: { min: -3, max: 3 }, + y: { min: 0, max: 3 }, + z: { min: -3, max: 3 }, + enabled: true, + }, + }; + constructor() { try { super({ diff --git a/src/experiences/home/world/scene-3.component.ts b/src/experiences/home/world/scene-3.component.ts index 8cf7b4d..1b9f8c4 100644 --- a/src/experiences/home/world/scene-3.component.ts +++ b/src/experiences/home/world/scene-3.component.ts @@ -9,6 +9,23 @@ import type { Materials } from "~/common/experiences/experience-world.model"; export class Scene3Component extends SceneComponentBlueprint { private _initialPcTopBone?: Object3D; + public readonly navigationLimits = { + spherical: { + radius: { min: 4, max: 8 }, + phi: { min: 0.01, max: Math.PI * 0.5 }, + theta: { min: 0, max: Math.PI * 0.5 }, + enabled: true, + enabledPhi: true, + enabledTheta: false, + }, + target: { + x: { min: -3, max: 3 }, + y: { min: 0, max: 3 }, + z: { min: -3, max: 3 }, + enabled: true, + }, + }; + public pcTopBone?: Object3D; constructor() { diff --git a/src/experiences/home/world/scene-container.component.ts b/src/experiences/home/world/scene-container.component.ts index fc8ac08..f581320 100644 --- a/src/experiences/home/world/scene-container.component.ts +++ b/src/experiences/home/world/scene-container.component.ts @@ -2,6 +2,8 @@ import { SceneComponentBlueprint } from "~/blueprints/experiences/scene-component.blueprint"; export class SceneContainerComponent extends SceneComponentBlueprint { + public readonly navigationLimits = undefined; + constructor() { try { super({