Skip to content

Commit

Permalink
Merge pull request #15 from readyplayerme/feature/binary-file-source
Browse files Browse the repository at this point in the history
Feature - binary file source
  • Loading branch information
Zaehiel authored Nov 4, 2022
2 parents 6e521c3 + c27d176 commit 25f08dc
Show file tree
Hide file tree
Showing 19 changed files with 349 additions and 99 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ npm install @readyplayerme/visage

Make sure to install peer-dependencies if your project doesn't already include them:
```sh
npm install @react-three/drei @react-three/fiber three three-stdlib
npm install @react-three/drei @react-three/fiber three three-stdlib suspend-react
```

# Documentation & examples
Expand All @@ -25,11 +25,11 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { Exhibit } from '@readyplayerme/visage';

const modelUrl = 'https://readyplayerme.github.io/visage/male.glb'; // this can be a relative or absolute URL
const modelSrc = 'https://readyplayerme.github.io/visage/male.glb'; // this can be a relative or absolute URL

function App() {
return (
<Exhibit modelUrl={modelUrl} />
<Exhibit modelSrc={modelSrc} />
);
}

Expand Down
53 changes: 53 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@
"react": ">=17.0",
"react-dom": ">=17.0",
"three": ">=0.138",
"three-stdlib": ">=2.8"
"three-stdlib": ">=2.8",
"suspend-react": "0.0.8"
},
"devDependencies": {
"@commitlint/cli": "^16.2.3",
Expand Down Expand Up @@ -120,6 +121,7 @@
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-terser": "^7.0.2",
"suspend-react": "0.0.8",
"three": "^0.138.3",
"three-stdlib": "^2.8.8",
"typescript": "^4.6.2",
Expand Down
4 changes: 2 additions & 2 deletions src/App/App.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ function App() {
<div className={styles.wrapper}>
<h3 className={styles.title}>localhost playground</h3>
<div className={styles.content}>
Paste your content in there to test the avatar props without shrinking the canvas
Drop your content in there to test the avatar props without shrinking the canvas
</div>
</div>
</div>
<div className={styles.container}>
<div className={styles.card}>
<Avatar modelUrl="/male.glb" poseUrl="/male-pose-standing.glb" backgroundColor="#fafafa" shadows={false} />
<Avatar modelSrc="/male.glb" poseSrc="/male-pose-standing.glb" backgroundColor="#fafafa" shadows={false} />
</div>
</div>
</div>
Expand Down
40 changes: 19 additions & 21 deletions src/components/Avatar/Avatar.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AnimationModel } from 'src/components/Models/AnimationModel/AnimationMo
import { LightingProps } from 'src/types';
import { BaseCanvas } from 'src/components/BaseCanvas';
import { HalfBodyModel, StaticModel, PoseModel } from 'src/components/Models';
import { isValidGlbUrl } from 'src/services';
import { isValidGlbFormat } from 'src/services';
import Capture, { CaptureType } from '../Capture/Capture.component';
import Box, { Background } from '../Background/Box/Box.component';
import Shadow from '../Shadow/Shadow.components';
Expand Down Expand Up @@ -40,21 +40,19 @@ export type Emotion = Record<string, number>;

export interface AvatarProps extends LightingProps {
/**
* Path to `.glb` file of the 3D model.
* Can be a relative or absolute URL.
* Arbitrary binary data (base64 string, Blob) of a `.glb` file or path (URL) to a `.glb` resource.
*/
modelUrl: string;
modelSrc: string | Blob;
/**
* Path to `.glb` animation file of the 3D model.
* Can be a relative or absolute URL.
* Arbitrary binary data (base64 string, Blob) of a `.glb` file or path (URL) to a `.glb` resource.
* The animation will be run for the 3D model provided in `modelSrc`.
*/
animationUrl?: string;
animationSrc?: string | Blob;
/**
* Path to `.glb` file which will be used to map Bone placements onto the underlying 3D model.
* Arbitrary binary data (base64 string, Blob) or a path (URL) to `.glb` file which will be used to map Bone placements onto the underlying 3D model.
* Applied when not specifying an animation.
* Can be a relative or absolute URL.
*/
poseUrl?: string;
poseSrc?: string | Blob;
/**
* Canvas background color. Supports all CSS color value types.
*/
Expand Down Expand Up @@ -115,9 +113,9 @@ export interface AvatarProps extends LightingProps {
* Optimised for full-body and half-body avatars.
*/
export const Avatar: FC<AvatarProps> = ({
modelUrl,
animationUrl = undefined,
poseUrl = undefined,
modelSrc,
animationSrc = undefined,
poseSrc = undefined,
backgroundColor = '#f0f0f0',
environment = 'city',
halfBody = false,
Expand All @@ -140,26 +138,26 @@ export const Avatar: FC<AvatarProps> = ({
loader
}) => {
const AvatarModel = useMemo(() => {
if (!isValidGlbUrl(modelUrl)) {
if (!isValidGlbFormat(modelSrc)) {
return null;
}

if (!!animationUrl && !halfBody && isValidGlbUrl(animationUrl)) {
if (!!animationSrc && !halfBody && isValidGlbFormat(animationSrc)) {
return (
<AnimationModel modelUrl={modelUrl} animationUrl={animationUrl} scale={scale} idleRotation={idleRotation} />
<AnimationModel modelSrc={modelSrc} animationSrc={animationSrc} scale={scale} idleRotation={idleRotation} />
);
}

if (halfBody) {
return <HalfBodyModel emotion={emotion} modelUrl={modelUrl} scale={scale} idleRotation={idleRotation} />;
return <HalfBodyModel emotion={emotion} modelSrc={modelSrc} scale={scale} idleRotation={idleRotation} />;
}

if (isValidGlbUrl(poseUrl)) {
return <PoseModel emotion={emotion} modelUrl={modelUrl} scale={scale} poseUrl={poseUrl!} />;
if (isValidGlbFormat(poseSrc)) {
return <PoseModel emotion={emotion} modelSrc={modelSrc} scale={scale} poseSrc={poseSrc!} />;
}

return <StaticModel modelUrl={modelUrl} scale={scale} />;
}, [halfBody, animationUrl, modelUrl, scale, poseUrl, idleRotation, emotion]);
return <StaticModel modelSrc={modelSrc} scale={scale} />;
}, [halfBody, animationSrc, modelSrc, scale, poseSrc, idleRotation, emotion]);

return (
<Suspense fallback={loader ?? <Loader />}>
Expand Down
63 changes: 41 additions & 22 deletions src/components/Avatar/Avatar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { getStoryAssetPath } from 'src/services';
import { Vector3 } from 'three';
import { FileDropper } from 'src/components/FileDropper/FileDropper.component';
import { Avatar, CAMERA } from './Avatar.component';

const Template: ComponentStory<typeof Avatar> = (args) => <Avatar {...args} />;
const DropTemplate: ComponentStory<typeof Avatar> = (args) => (
<FileDropper>
<Avatar {...args} />
</FileDropper>
);

export const Static = Template.bind({});
Static.args = {
backgroundColor: '#f0f0f0',
modelUrl: getStoryAssetPath('female.glb'),
modelSrc: getStoryAssetPath('female.glb'),
scale: 1,
environment: 'city',
shadows: false,
Expand All @@ -26,35 +32,19 @@ Static.args = {
cameraInitialDistance: CAMERA.CONTROLS.FULL_BODY.MAX_DISTANCE
};

export default {
title: 'Components/Avatar',
component: Avatar,
argTypes: {
backgroundColor: { control: 'color' },
ambientLightColor: { control: 'color' },
dirLightColor: { control: 'color' },
spotLightColor: { control: 'color' },
ambientLightIntensity: { control: { type: 'range', min: 0, max: 10, step: 0.1 } },
spotLightAngle: { control: { type: 'range', min: 0, max: 10, step: 0.01 } },
cameraTarget: { control: { type: 'range', min: 0, max: 10, step: 0.01 } },
scale: { control: { type: 'range', min: 0.01, max: 10, step: 0.01 } },
cameraInitialDistance: { control: { type: 'range', min: 0, max: 2.5, step: 0.01 } }
}
} as ComponentMeta<typeof Avatar>;

export const Animated = Template.bind({});
Animated.args = {
...Static.args,
modelUrl: getStoryAssetPath('male.glb'),
animationUrl: getStoryAssetPath('male-idle.glb'),
modelSrc: getStoryAssetPath('male.glb'),
animationSrc: getStoryAssetPath('male-idle.glb'),
cameraTarget: CAMERA.TARGET.FULL_BODY,
cameraInitialDistance: CAMERA.CONTROLS.FULL_BODY.MAX_DISTANCE
};

export const HalfBody = Template.bind({});
HalfBody.args = {
...Static.args,
modelUrl: getStoryAssetPath('half-body.glb'),
modelSrc: getStoryAssetPath('half-body.glb'),
halfBody: true,
cameraTarget: CAMERA.TARGET.HALF_BODY,
cameraInitialDistance: CAMERA.INITIAL_DISTANCE.HALF_BODY
Expand All @@ -63,8 +53,37 @@ HalfBody.args = {
export const Posing = Template.bind({});
Posing.args = {
...Static.args,
modelUrl: getStoryAssetPath('male.glb'),
poseUrl: getStoryAssetPath('male-pose-standing.glb'),
modelSrc: getStoryAssetPath('male.glb'),
poseSrc: getStoryAssetPath('male-pose-standing.glb'),
cameraTarget: CAMERA.TARGET.FULL_BODY,
cameraInitialDistance: CAMERA.CONTROLS.FULL_BODY.MAX_DISTANCE
};

/* eslint-disable */
export const _BinaryInput = DropTemplate.bind({});
_BinaryInput.args = {
...Static.args,
modelSrc: '',
cameraTarget: CAMERA.TARGET.FULL_BODY,
cameraInitialDistance: CAMERA.CONTROLS.FULL_BODY.MAX_DISTANCE
};
_BinaryInput.argTypes = {
modelSrc: { control: false }
};
/* eslint-enable */

export default {
title: 'Components/Avatar',
component: Avatar,
argTypes: {
backgroundColor: { control: 'color' },
ambientLightColor: { control: 'color' },
dirLightColor: { control: 'color' },
spotLightColor: { control: 'color' },
ambientLightIntensity: { control: { type: 'range', min: 0, max: 10, step: 0.1 } },
spotLightAngle: { control: { type: 'range', min: 0, max: 10, step: 0.01 } },
cameraTarget: { control: { type: 'range', min: 0, max: 10, step: 0.01 } },
scale: { control: { type: 'range', min: 0.01, max: 10, step: 0.01 } },
cameraInitialDistance: { control: { type: 'range', min: 0, max: 2.5, step: 0.01 } }
}
} as ComponentMeta<typeof Avatar>;
Loading

0 comments on commit 25f08dc

Please sign in to comment.