Skip to content

Commit

Permalink
Merge pull request #7 from Shubidumdu/spinner
Browse files Browse the repository at this point in the history
Add new sketch "Spinner"
  • Loading branch information
Shubidumdu authored Oct 25, 2023
2 parents 836b3ba + 1004ada commit 636c77a
Show file tree
Hide file tree
Showing 14 changed files with 259 additions and 5 deletions.
6 changes: 5 additions & 1 deletion ext.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ declare module '*.fx' {
export default value;
}


declare module '*.fx?raw' {
const value: string;
export default value;
}

declare module '*.mp3' {
const value: string;
export default value;
}
15 changes: 11 additions & 4 deletions pages/sculpture/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@babylonjs/core';
import '@babylonjs/loaders';
import './style.scss';
import sculpturePath from './sculpture.glb';
import sculpturePath from './sculpture.glb';

const rootUrl = sculpturePath.split('/');
const sceneFile = rootUrl.pop();
Expand All @@ -17,7 +17,14 @@ const main = async () => {
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const engine = new Engine(canvas, true);
const scene = new Scene(engine);
const camera = new ArcRotateCamera('camera', 2.24, 1.68, 7, Vector3.Zero(), scene);
const camera = new ArcRotateCamera(
'camera',
2.24,
1.68,
7,
Vector3.Zero(),
scene,
);
camera.target = Vector3.Zero();
camera.attachControl(canvas, true);
engine.displayLoadingUI();
Expand All @@ -28,7 +35,7 @@ const main = async () => {
sceneFile,
scene,
function () {
const [headMaterial, eyeMaterial] = scene.materials as PBRMaterial[];
const [headMaterial, eyeMaterial] = scene.materials as PBRMaterial[];
headMaterial.metallic = 1;
headMaterial.roughness = 0.3;
eyeMaterial.metallic = 1;
Expand All @@ -45,6 +52,6 @@ const main = async () => {
scene.render();
engine.resize();
});
};
};

main();
15 changes: 15 additions & 0 deletions pages/spinner/camera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ArcRotateCamera, Scene, Vector3 } from '@babylonjs/core';

export const setupCamera = (scene: Scene) => {
const camera = new ArcRotateCamera(
'camera',
Math.PI / 2,
0.87,
15,
Vector3.Zero(),
scene,
);
camera.target = Vector3.Zero();
camera.attachControl(scene.getEngine().getRenderingCanvas(), true);
return camera;
};
72 changes: 72 additions & 0 deletions pages/spinner/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Mesh, Scene, Vector3 } from '@babylonjs/core';
import { State } from './state';

export const setupEventHandlers = (
scene: Scene,
state: State,
spinner: Mesh,
) => {
const canvas = scene.getEngine().getRenderingCanvas();
const camera = scene.activeCamera;

if (!canvas || !camera) {
console.log('No canvas or camera');
return;
}

const handlePointerUp = () => {
const point = scene.pick(scene.pointerX, scene.pointerY).pickedPoint;
if (point && state.startPickInfo) {
const _point = point.multiplyToRef(new Vector3(1, 0, 1), new Vector3());
const _startPoint = state.startPickInfo.pickedPoint!.multiplyToRef(
new Vector3(1, 0, 1),
new Vector3(),
);
const distance = Vector3.Distance(_point, _startPoint);
const cross = Vector3.Cross(_point, _startPoint);
const direction = cross.y > 0 ? -1 : 1;
const time = performance.now() - state.startPickTime;
state.velocity = direction * (distance / time) * 50;
}
state.startPickInfo = null;
state.endPickTime = performance.now();
camera.attachControl(canvas, true);
canvas.removeEventListener('pointermove', handlePointerMove);
canvas.removeEventListener('pointerup', handlePointerUp);
};

const handlePointerMove = () => {
const pickInfo = scene.pick(scene.pointerX, scene.pointerY);
const point = pickInfo.pickedPoint;
if (point && state.startPickInfo) {
const _point = point
.multiplyToRef(new Vector3(1, 0, 1), new Vector3())
.normalize();
const _startPoint = state.startPickInfo
.pickedPoint!.multiplyToRef(new Vector3(1, 0, 1), new Vector3())
.normalize();
const dot = Vector3.Dot(_point, _startPoint);
const rad = Math.acos(dot);
const cross = Vector3.Cross(_point, _startPoint);
const angle = cross.y > 0 ? -rad : rad;
spinner.rotation.y = state.startAngle + angle;
}
};

const handlePointerDown = () => {
const pickInfo = scene.pick(scene.pointerX, scene.pointerY);
if (pickInfo.pickedMesh?.name) {
if (!pickInfo.pickedMesh.name.includes('Background')) {
camera.detachControl();
state.startPickInfo = pickInfo;
state.velocity = 0;
state.startAngle = spinner.rotation.y;
state.startPickTime = performance.now();
canvas.addEventListener('pointermove', handlePointerMove);
canvas.addEventListener('pointerup', handlePointerUp);
}
}
};

canvas.addEventListener('pointerdown', handlePointerDown);
};
26 changes: 26 additions & 0 deletions pages/spinner/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spinner</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="icon" type="image/x-icon" href="/favicon.ico">
</head>
<body>
<canvas id="canvas"></canvas>
<footer>
Spinner model by
<a href="https://sketchfab.com/3d-models/spinner-d850bdc2301348259209cca806c470a7"
target="_blank"
rel="noopener noreferrer"
>TecDesign</a>,
<a href="https://creativecommons.org/licenses/by/4.0/"
target="_blank"
rel="noopener noreferrer"
>CC BY 4.0 DEED</a>
</footer>
<script type="module" src="./main.ts"></script>
</body>
</html>
38 changes: 38 additions & 0 deletions pages/spinner/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import '@babylonjs/loaders';
import './style.scss';
import { Color3, Color4, Engine, Scene } from '@babylonjs/core';
import { setupCamera } from './camera';
import { loadModel } from './meshes';
import { setupEventHandlers } from './event';
import { state } from './state';
import { setupMotionBlur } from './postProcess';
import { calcAngularVelocity } from './util';

const main = async () => {
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const engine = new Engine(canvas, true);
const scene = new Scene(engine);
scene.clearColor = Color4.FromHexString('#212E33');
const { spinner } = await loadModel(scene);
setupCamera(scene);
setupMotionBlur(scene);
setupEventHandlers(scene, state, spinner);
const environment = scene.createDefaultEnvironment({
createGround: true,
createSkybox: false,
});
if (environment?.groundMaterial) {
environment.groundMaterial.primaryColor = Color3.FromHexString('#637A82');
}
scene.createDefaultLight(true);
engine.hideLoadingUI();

engine.runRenderLoop(() => {
const velocity = calcAngularVelocity(state.velocity, state.endPickTime);
spinner.rotation.y += velocity;
scene.render();
engine.resize();
});
};

main();
45 changes: 45 additions & 0 deletions pages/spinner/meshes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
Mesh,
MeshBuilder,
Scene,
SceneLoader,
StandardMaterial,
} from '@babylonjs/core';
import spinnerPath from './spinner.glb';

const rootUrl = spinnerPath.split('/');
const sceneFile = rootUrl.pop();

export const loadModel = async (scene: Scene) => {
const result = await SceneLoader.ImportMeshAsync(
'Spinner',
rootUrl.join('/') + '/',
sceneFile,
scene,
);

const [root] = result.meshes;
const [spinner] = root.getChildren() as Mesh[];
spinner.parent = null;

root.dispose();

spinner.rotationQuaternion = null;

return {
spinner,
cylinder: setupClickableCylinder(scene),
};
};

const setupClickableCylinder = (scene: Scene) => {
const cylinder = MeshBuilder.CreateCylinder('ClickableCylinder', {
diameter: 9.5,
height: 1.2,
tessellation: 32,
});
const material = new StandardMaterial('ClickableMaterial', scene);
material.alpha = 0;
cylinder.material = material;
return cylinder;
};
Empty file added pages/spinner/motionBlur.ts
Empty file.
13 changes: 13 additions & 0 deletions pages/spinner/postProcess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MotionBlurPostProcess, Scene } from '@babylonjs/core';

export const setupMotionBlur = (scene: Scene) => {
const camera = scene.activeCamera;
const motionBlur = new MotionBlurPostProcess(
'motionBlur',
scene,
1.0,
camera,
);
motionBlur.motionStrength = 0.1;
return motionBlur;
};
Binary file added pages/spinner/spinner.glb
Binary file not shown.
19 changes: 19 additions & 0 deletions pages/spinner/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Nullable, PickingInfo } from '@babylonjs/core';

export type State = {
startPickInfo: Nullable<PickingInfo>;
startPickTime: number;
endPickTime: number;
velocity: number;
startAngle: number;
};

const state: State = {
startPickInfo: null,
startPickTime: 0,
endPickTime: 0,
velocity: 0,
startAngle: 0,
};

export { state };
7 changes: 7 additions & 0 deletions pages/spinner/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
footer {
position: fixed;
bottom: 1rem;
left: 1rem;
font-size: 0.8rem;
color: white;
}
8 changes: 8 additions & 0 deletions pages/spinner/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const calcAngularVelocity = (
initialVelocity: number,
startTime: number,
) => {
const decayConstant = 0.1;
const time = (performance.now() - startTime) * 0.001;
return initialVelocity * Math.exp(-decayConstant * time);
};
Binary file added public/thumbnails/spinner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 636c77a

Please sign in to comment.