Skip to content

Commit

Permalink
Bricks: restucturing scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
tmunz committed Nov 1, 2024
1 parent 8cd5aa6 commit 1617efa
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 91 deletions.
6 changes: 3 additions & 3 deletions src/app/content/art/Art.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import './Art.styl';
import React from 'react';
import React, { useRef } from 'react';
import { DragBoard } from '../../ui/drag-board/DragBoard';
import { DrawBoardItem } from './draw/DrawBoardItem';
import { useDimension } from '../../utils/useDimension';
Expand All @@ -10,7 +10,7 @@ import { Mb300slPainting } from './mb300sl/Mb300slPainting';

export function Art() {

const elementRef = React.useRef<HTMLDivElement>(null);
const elementRef = useRef<HTMLDivElement>(null);
const dimension = useDimension(elementRef, 40);

return <div className='art' ref={elementRef}>
Expand All @@ -20,7 +20,7 @@ export function Art() {
>
<DragBoardItem><CitroenDsLamp width={(dimension?.width ?? 600) * 0.6} height={(dimension?.height ?? 400) * 0.6} /></DragBoardItem>
<DrawBoardItem width={dimension?.width ?? 600} height={dimension?.height ?? 400} />
<DragBoardItem><Mb300slPainting width={Math.max(500, (dimension?.width ?? 500) * 0.3)}/></DragBoardItem>
<DragBoardItem><Mb300slPainting width={Math.max(500, (dimension?.width ?? 500) * 0.3)} /></DragBoardItem>
</DragBoard>
</div>;
}
4 changes: 4 additions & 0 deletions src/app/content/bricks/Bricks.styl
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@

margin-left: calc(-1 * var(--frame-title-size));
margin-top: calc(-1 * var(--frame-close-button-size));

section {
padding-left: var(--frame-title-size);
}
}
133 changes: 78 additions & 55 deletions src/app/content/bricks/Bricks.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
import './Bricks.styl';
import React from 'react';
import React, { useRef } from 'react';
import { Canvas } from '@react-three/fiber';
import { ScrollControls, Environment, Scroll, View, Preload, PivotControls, OrbitControls, PerspectiveCamera } from '@react-three/drei';
import { Environment, View, Preload, PerspectiveCamera } from '@react-three/drei';
import { BrickScroll } from './brick/BrickScroll';
import { Transformations } from '../../utils/TransformationAnimator';
import { Mb300slScroll } from './mb300sl/Mb300slScroll';
import { LoadingBrick } from './LoadingBrick';
import { useLoadable } from './useLoadable';
import { Duck, Soda } from './Models';
import { useDimension } from '../../utils/useDimension';
import { useScroll } from '../../utils/useScroll';
import { Mb300slContent } from './mb300sl/Mb300slContent';
import { AircraftContent } from './aircraft/AircraftContent';
import { AircraftScroll } from './aircraft/AircraftScroll';
import { Apple, Soda } from './Models';
type TransformableObject = 'brick' | 'mb300sl' | 'aircraft';

const SECTIONS = [
<AircraftContent />,
<Mb300slContent />,
'empty',
'empty',
'',
< View style={{ height: 300 }}>
<color attach="background" args={["lightblue"]} />
<ambientLight intensity={0.5} />
<pointLight position={[20, 30, 10]} intensity={1} />
<pointLight position={[-10, -10, -10]} color="blue" />
<Environment preset="dawn" />
<PerspectiveCamera makeDefault fov={40} position={[0, 0, 6]} />
<Duck position={[0, -1, -5]}
/>
</View >,
<View style={{ height: 300 }}>
<color attach="background" args={["green"]} />
<ambientLight intensity={0.5} />
<pointLight position={[20, 30, 10]} intensity={1} />
<pointLight position={[-10, -10, -10]} color="blue" />
<Environment preset="dawn" />
<PerspectiveCamera makeDefault fov={40} position={[0, 0, 6]} />
<Soda position={[0, -1, 1]} scale={14} />
</View>,
'empty',
];

Expand All @@ -26,10 +42,11 @@ const L = SECTIONS.length;
const MB_300SL_TRIGGER: [number, number] = [1.3 / L, 2.6 / L];

// key is ratio of the scrollPosition [0, 1], x/L represents the x-th section
const SCROLL_STATES: Record<TransformableObject, Transformations> = {
const SCROLL_STATES: Record<string, Transformations> = {
brick: new Map([
[0.1 / L, { rotateX: 0.25, rotateY: Math.PI / 5, scaleX: 4, scaleY: 4, scaleZ: 4, positionX: 0.1, positionY: -1.8, positionZ: 2 }],
[1.0 / L, { rotateX: 0, rotateY: 0, positionY: 1.25 }],
[0, { rotateX: 0.25, rotateY: Math.PI / 5, scaleX: 3.2, scaleY: 3.2, scaleZ: 3.2, positionX: 0.05, positionY: -1.2, positionZ: 3 }],
[1 / L, { rotateX: 0, rotateY: 0, positionY: 0.0 }],
[1.3 / L, { rotateX: 0, rotateY: 0, positionY: 0.9 }],
[0.9, { rotateX: Math.PI / 2, rotateY: Math.PI, positionX: 0, positionY: 0 }],
[1.0, { rotateX: 0, rotateY: Math.PI * 2, positionX: 0, positionY: -1 }],
]),
Expand All @@ -44,52 +61,58 @@ const SCROLL_STATES: Record<TransformableObject, Transformations> = {
};

export const Bricks = () => {
let [loadables, loaded] = useLoadable([
(onLoadComplete) => <BrickScroll transformations={SCROLL_STATES.brick} onLoadComplete={onLoadComplete} />,
(onLoadComplete) => <Mb300slScroll transformations={SCROLL_STATES.mb300sl} onLoadComplete={onLoadComplete} animationTrigger={[MB_300SL_TRIGGER[0] - 1 / L, MB_300SL_TRIGGER[1] + 1 / L]} />,
(onLoadComplete) => <AircraftScroll transformations={SCROLL_STATES.aircraft} onLoadComplete={onLoadComplete} />,

const elementRef = useRef<HTMLDivElement>(null);
const dimension = useDimension(elementRef);
const scroll = useScroll(elementRef);

console.log('scroll', scroll);

const [loadables, loaded] = useLoadable([
// (onLoadComplete) => <AircraftScroll transformations={SCROLL_STATES.aircraft} onLoadComplete={onLoadComplete} />,
]);

loaded = true;
return <div className='bricks'>
{!loaded && <LoadingBrick />}

return (
<div className='bricks'>
{!loaded && <LoadingBrick />}
<div
ref={elementRef}
style={{
overflow: 'auto',
opacity: loaded ? 1 : 0,
transition: 'opacity 0.5s ease-in-out',
width: '100%',
height: '100%',
}}
>
{SECTIONS.map((section, i) => {
return <section key={i} style={{ height: '100vh', overflow: 'hidden', position: 'relative' }}>{
(i === 1) ?
<>
<Mb300slContent />
<View style={{ height: '100%', width: '100vw', position: 'absolute', top: 0, left: 0 }}>
<Mb300slScroll transformations={SCROLL_STATES.mb300sl} progress={scroll} animationTrigger={[MB_300SL_TRIGGER[0] - 1 / L, MB_300SL_TRIGGER[1] + 1 / L]} />
</View>
</>
: section
}</section>
})}

<div
style={{
opacity: loaded ? 1 : 0,
transition: 'opacity 0.5s ease-in-out',
width: '100%',
height: '100%',
}}
>
{/**************************************** overlay *******************************************/}
<View style={{ width: dimension?.width ?? 600, height: dimension?.height ?? 800, position: 'absolute', top: 0, left: 0, zIndex: -1 }}>
<ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 5]} intensity={1} />
<PerspectiveCamera makeDefault fov={12} position={[0, 0, 10]} />
<BrickScroll transformations={SCROLL_STATES.brick} progress={scroll} />
<Environment preset='sunset' />
</View>

<Canvas camera={{ position: [0, 0, 9], fov: 14 }} frameloop='demand'>
<ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 5]} intensity={1} />
<ScrollControls pages={SECTIONS.length} damping={0.5}>
{loadables}
<Scroll html>
{SECTIONS.map((section, i) => (
<section
key={i}
style={{
marginTop: 40,
height: '100vh',
width: '100vw',
background: i % 2 ? 'rgba(0, 0, 0, 0.1)' : 'none',
// pointerEvents: 'none',
}}
>
{section}
</section>
))}
</Scroll>
</ScrollControls>
<Environment preset='sunset' />
</Canvas>
</div>
</div>
);
</div >
<Canvas
style={{ position: 'fixed', top: 0, bottom: 0, left: 0, right: 0, overflow: 'hidden', zIndex: -1 }}
eventSource={document.getElementById('poc')!}>
<View.Port />
<Preload all />
</Canvas>
</div >;
};
7 changes: 2 additions & 5 deletions src/app/content/bricks/brick/BrickScroll.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import React, { useRef } from "react";
import { useScroll } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import { Object3D } from "three";
import { Brick } from "./Brick";
import { useTransformationAnimator, Transformations } from "../../../utils/TransformationAnimator";

export function BrickScroll({ transformations, onLoadComplete }: { transformations: Transformations, onLoadComplete: () => void }) {
export function BrickScroll({ transformations, progress }: { transformations: Transformations, progress: number }) {
const ref = useRef<Object3D>(null!);
const scroll = useScroll();
const transformationAnimator = useTransformationAnimator(transformations);

useFrame(() => {
if (ref.current) {
transformationAnimator.apply(ref.current, scroll.offset);
transformationAnimator.apply(ref.current, progress, 0.92);
}
});

return <Brick onLoadComplete={(model) => {
ref.current = model;
onLoadComplete();
}} />;
}
9 changes: 8 additions & 1 deletion src/app/content/bricks/mb300sl/Mb300slContent.styl
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
.mb-300sl-content {
color: white;
font-size: 100px;
opacity: 0.9;
font-size: 15vw;
padding-top: 30px;
font-weight: 900;
height: 100%;
display: flex;
justify-content: left;
align-items: top;
mix-blend-mode: difference;
}
19 changes: 5 additions & 14 deletions src/app/content/bricks/mb300sl/Mb300slScroll.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
import React, { useRef } from 'react';
import { Transformations, useTransformationAnimator } from '../../../utils/TransformationAnimator';
import { RenderTexture, useScroll } from '@react-three/drei';
import { CarShow } from '../../../three/car-show/CarShow';
import { Mb300sl } from './Mb300sl';
import { Quality } from '../../../three/QualityProvider';
import { useFrame } from '@react-three/fiber';
import { Mesh } from 'three';
import { FullscreenPlane } from '../../../three/FullscreenPlane';

export function Mb300slScroll({ transformations, onLoadComplete, animationTrigger }: { transformations: Transformations, onLoadComplete: () => void, animationTrigger: [number, number] }) {
export function Mb300slScroll({ transformations, progress, animationTrigger }: { transformations: Transformations, progress: number, animationTrigger: [number, number] }) {
const elementRef = useRef<Mesh | null>(null);
const carShowRef = useRef<{ controls: any }>(null);
const scroll = useScroll();
const transformationAnimator = useTransformationAnimator(transformations);
const [animate, setAnimate] = React.useState(true);

useFrame(() => {
const top = animationTrigger[0];
const bottom = animationTrigger[1];
if (top <= scroll.offset && scroll.offset <= bottom && !animate) {
if (top <= progress && progress <= bottom && !animate) {
setAnimate(true);
} else if ((scroll.offset < top || bottom < scroll.offset) && animate) {
} else if ((progress < top || bottom < progress) && animate) {
setAnimate(false);
}
if (elementRef.current) {
transformationAnimator.apply(elementRef.current, scroll.offset);
transformationAnimator.apply(elementRef.current, progress);
}
if (carShowRef.current) {
// carShowRef.current.controls.setAzimuthalAngle(scroll.offset * 10);
Expand All @@ -33,12 +30,6 @@ export function Mb300slScroll({ transformations, onLoadComplete, animationTrigge


return (
<FullscreenPlane ref={elementRef}>
<meshStandardMaterial>
<RenderTexture attach='map'>
<CarShow ref={carShowRef} Model={Mb300sl} animate={animate} quality={Quality.HIGH} onLoadComplete={onLoadComplete} controls={false} />
</RenderTexture>
</meshStandardMaterial>
</FullscreenPlane>
<CarShow ref={carShowRef} Model={Mb300sl} animate={animate} quality={Quality.HIGH} controls={true} />
);
};
6 changes: 3 additions & 3 deletions src/app/content/bricks/useLoadable.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useProgress } from "@react-three/drei";
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";

export type Loadable = (onLoadComplete: () => void) => React.ReactElement<{ onLoadComplete: () => void }>

export function useLoadable(loadables: Loadable[]): [React.ReactElement[], boolean] {

const { active, progress } = useProgress();
const [loaded, setLoaded] = useState(false);
const [loaded, setLoaded] = useState(loadables.length === 0 ? true : false);
const completedRef = useRef(Array.from({ length: loadables.length }, () => false));
const loadablesRef = useRef(loadables.map((l, i) => l(() => onLoadedCompleted(i))));

Expand All @@ -17,5 +17,5 @@ export function useLoadable(loadables: Loadable[]): [React.ReactElement[], boole
}
}

return [loadablesRef.current, loaded && !active && progress === 100];
return [loadablesRef.current, loaded];
}
14 changes: 14 additions & 0 deletions src/app/content/vita/Vita.styl
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
.vita-frame {
.background-image {
transition: filter 0.8s ease;

&.parallax {
transition: transform 0.2s ease, filter 0.8s ease;
}
}

&.active .background-image {
filter: blur(100px); /*contrast(1.0) brightness(1.0) saturate(1.0)*/
}
}

.vita {
width: 100%;
height: 100%;
Expand Down
33 changes: 23 additions & 10 deletions src/app/utils/TransformationAnimator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export type TransformationMap = Map<number, Transformation>;

export function useTransformationAnimator(transformations: Transformations | TransformationMap) {

const lerp = (start: number, end: number, factor: number) => {
return start + (end - start) * factor;
};

const convertToTransformationMap = (raw: Transformations): TransformationMap => {
const fullTransformation = (
curr: Partial<Transformation>,
Expand Down Expand Up @@ -60,10 +64,6 @@ export function useTransformationAnimator(transformations: Transformations | Tra
return transformationsMap.get(keys[keys.length - 1]) ?? neutralTransformation;
}

const lerp = (start: number, end: number, factor: number) => {
return start + (end - start) * factor;
};

for (let i = 0; i < keys.length - 1; i++) {
const startKey = keys[i];
const endKey = keys[i + 1];
Expand All @@ -84,13 +84,26 @@ export function useTransformationAnimator(transformations: Transformations | Tra
const transformationsMap: TransformationMap = convertToTransformationMap(transformations);

return {
get: (t: number) => get(t),
get: (t?: number) => get(t),
getTransformationsMap: () => transformationsMap,
apply: (obj: Object3D, t: number) => {
const state = get(t);
obj.rotation.set(state.rotateX, state.rotateY, state.rotateZ);
obj.scale.set(state.scaleX, state.scaleY, state.scaleZ);
obj.position.set(state.positionX, state.positionY, state.positionZ);
apply: (obj: Object3D, t: number, damping: number = 0, fadeIn: number = damping) => {
const targetState = get(t);
const factor = Math.min((1 - (t === 0 ? fadeIn : damping)), 1);
obj.rotation.set(
lerp(obj.rotation.x, targetState.rotateX, factor),
lerp(obj.rotation.y, targetState.rotateY, factor),
lerp(obj.rotation.z, targetState.rotateZ, factor)
);
obj.scale.set(
lerp(obj.scale.x, targetState.scaleX, factor),
lerp(obj.scale.y, targetState.scaleY, factor),
lerp(obj.scale.z, targetState.scaleZ, factor)
);
obj.position.set(
lerp(obj.position.x, targetState.positionX, factor),
lerp(obj.position.y, targetState.positionY, factor),
lerp(obj.position.z, targetState.positionZ, factor)
);
}
};
}
Loading

0 comments on commit 1617efa

Please sign in to comment.