Skip to content

Commit

Permalink
Make PanoData more flexible
Browse files Browse the repository at this point in the history
  • Loading branch information
mistic100 committed Oct 13, 2024
1 parent b7c79fd commit 98cf4d3
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 80 deletions.
14 changes: 5 additions & 9 deletions docs/guide/adapters/equirectangular-video.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,11 @@ Can by used to define cropping information if the video does not cover a full sp
```js
panorama: {
source: 'path/video.mp4',
data: (video) => {
return {
fullWidth: 6000,
fullHeight: 3000,
croppedWidth: video.videoWidth,
croppedHeight: video.videoHeight,
croppedX: (6000 - video.videoWidth) / 2,
croppedY: (3000 - video.videoHeight) / 2,
};
data: {
fullWidth: 6000,
// "fullHeight" optional, always "fullWidth / 2"
croppedX: 1000,
croppedY: 500,
},
}
```
6 changes: 3 additions & 3 deletions docs/guide/adapters/equirectangular.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ const viewer = new Viewer({
// cropping information
panoData: {
fullWidth: 6000,
fullHeight: 3000,
croppedWidth: 4000,
croppedHeight: 2000,
fullHeight: 3000, // optional
croppedWidth: 4000, // optional
croppedHeight: 2000, // optional
croppedX: 1000,
croppedY: 500,
},
Expand Down
8 changes: 4 additions & 4 deletions docs/guide/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,15 +222,15 @@ All parameters are optional.
```js
panoData: {
fullWidth: 6000,
fullHeight: 3000,
croppedWidth: 4000,
croppedHeight: 2000,
fullHeight: 3000, // optional
croppedWidth: 4000, // optional
croppedHeight: 2000, // optional
croppedX: 1000,
croppedY: 500,
}
```

It can also be a function to dynamically compute the cropping config depending on the loaded image.
It can also be a function to dynamically compute the cropping config depending on the loaded image (note that a [default setting](./adapters/equirectangular.md#default-parameters) is already applied when no data is found).

```js
panoData: (image, xmpData) => ({
Expand Down
59 changes: 2 additions & 57 deletions packages/core/src/adapters/EquirectangularAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Viewer } from '../Viewer';
import { SPHERE_RADIUS } from '../data/constants';
import { SYSTEM } from '../data/system';
import { EquirectangularPanorama, PanoData, PanoDataProvider, PanoramaPosition, Position, TextureData } from '../model';
import { createTexture, firstNonNull, getConfigParser, getXMPValue, isNil, logWarn } from '../utils';
import { createTexture, getConfigParser, getXMPValue, isNil, logWarn, mergePanoData } from '../utils';
import { AbstractAdapter } from './AbstractAdapter';

/**
Expand Down Expand Up @@ -166,7 +166,7 @@ export class EquirectangularAdapter extends AbstractAdapter<string | Equirectang
cleanPanorama.data = cleanPanorama.data(img, xmpPanoData);
}

const panoData = this.mergePanoData(img.width, img.height, cleanPanorama.data, xmpPanoData);
const panoData = mergePanoData(img.width, img.height, cleanPanorama.data, xmpPanoData);

const texture = this.createEquirectangularTexture(img);

Expand Down Expand Up @@ -292,59 +292,4 @@ export class EquirectangularAdapter extends AbstractAdapter<string | Equirectang
mesh.material.dispose();
}

/**
* @internal
*/
mergePanoData(width: number, height: number, newPanoData?: PanoData, xmpPanoData?: PanoData): PanoData {
if (!newPanoData && !xmpPanoData) {
const fullWidth = Math.max(width, height * 2);
const fullHeight = Math.round(fullWidth / 2);
const croppedX = Math.round((fullWidth - width) / 2);
const croppedY = Math.round((fullHeight - height) / 2);

newPanoData = {
fullWidth: fullWidth,
fullHeight: fullHeight,
croppedWidth: width,
croppedHeight: height,
croppedX: croppedX,
croppedY: croppedY,
};
}

const panoData: PanoData = {
isEquirectangular: true,
fullWidth: firstNonNull(newPanoData?.fullWidth, xmpPanoData?.fullWidth, width),
fullHeight: firstNonNull(newPanoData?.fullHeight, xmpPanoData?.fullHeight, height),
croppedWidth: firstNonNull(newPanoData?.croppedWidth, xmpPanoData?.croppedWidth, width),
croppedHeight: firstNonNull(newPanoData?.croppedHeight, xmpPanoData?.croppedHeight, height),
croppedX: firstNonNull(newPanoData?.croppedX, xmpPanoData?.croppedX, 0),
croppedY: firstNonNull(newPanoData?.croppedY, xmpPanoData?.croppedY, 0),
poseHeading: firstNonNull(newPanoData?.poseHeading, xmpPanoData?.poseHeading, 0),
posePitch: firstNonNull(newPanoData?.posePitch, xmpPanoData?.posePitch, 0),
poseRoll: firstNonNull(newPanoData?.poseRoll, xmpPanoData?.poseRoll, 0),
initialHeading: xmpPanoData?.initialHeading,
initialPitch: xmpPanoData?.initialPitch,
initialFov: xmpPanoData?.initialFov,
};

if (panoData.croppedWidth !== width || panoData.croppedHeight !== height) {
logWarn(`Invalid panoData, croppedWidth/croppedHeight is not coherent with the loaded image.
panoData: ${panoData.croppedWidth}x${panoData.croppedHeight}, image: ${width}x${height}`);
}
if (Math.abs(panoData.fullWidth - panoData.fullHeight * 2) > 1) {
logWarn('Invalid panoData, fullWidth should be twice fullHeight');
panoData.fullWidth = panoData.fullHeight * 2;
}
if (panoData.croppedX + panoData.croppedWidth > panoData.fullWidth) {
logWarn('Invalid panoData, croppedX + croppedWidth > fullWidth');
panoData.croppedX = panoData.fullWidth - panoData.croppedWidth;
}
if (panoData.croppedY + panoData.croppedHeight > panoData.fullHeight) {
logWarn('Invalid panoData, croppedY + croppedHeight > fullHeight');
panoData.croppedY = panoData.fullHeight - panoData.croppedHeight;
}

return panoData;
}
}
6 changes: 3 additions & 3 deletions packages/core/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ export type EquirectangularPanorama = {
export type PanoData = {
isEquirectangular?: true;
fullWidth: number;
fullHeight: number;
croppedWidth: number;
croppedHeight: number;
fullHeight?: number;
croppedWidth?: number;
croppedHeight?: number;
croppedX: number;
croppedY: number;
poseHeading?: number;
Expand Down
190 changes: 189 additions & 1 deletion packages/core/src/utils/psv.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from 'assert';
import { cleanCssPosition, getXMPValue, parseAngle, parsePoint, parseSpeed, speedToDuration } from './psv';
import { cleanCssPosition, getXMPValue, mergePanoData, parseAngle, parsePoint, parseSpeed, speedToDuration } from './psv';
import { PanoData } from '../model';

describe('utils:psv:parseAngle', () => {
it('should normalize number', () => {
Expand Down Expand Up @@ -379,3 +380,190 @@ describe('utils:psv:speedToDuration', () => {
assert.strictEqual(speedToDuration('2rpm', Math.PI), 15000);
});
});

describe('utils:psv:mergePanoData', () => {
it('should generate default panoData for 2:1 image', () => {
assertDeepEqualLenient(mergePanoData(2000, 1000), {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 2000,
croppedHeight: 1000,
croppedX: 0,
croppedY: 0,
} satisfies PanoData);
});

it('should generate default panoData for partial image (horizontal)', () => {
assertDeepEqualLenient(mergePanoData(2000, 500), {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 0,
croppedY: 250,
} satisfies PanoData);
});

it('should generate default panoData for partial image (vertical)', () => {
assertDeepEqualLenient(mergePanoData(1000, 1000), {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 1000,
croppedHeight: 1000,
croppedX: 500,
croppedY: 0,
} satisfies PanoData);
});

it('should keep XMP data', () => {
assertDeepEqualLenient(mergePanoData(2000, 500, undefined, {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 0,
croppedY: 500,
}), {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 0,
croppedY: 500,
} satisfies PanoData);
});

it('should keep custom data over XMP', () => {
assertDeepEqualLenient(mergePanoData(2000, 500, {
fullWidth: 3000,
fullHeight: 1500,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 500,
croppedY: 1000,
}, {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 0,
croppedY: 500,
}), {
fullWidth: 3000,
fullHeight: 1500,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 500,
croppedY: 1000,
} satisfies PanoData);
});

it('should fix invalid fullWidth/fullHeight', () => {
assertDeepEqualLenient(mergePanoData(2000, 500, {
fullWidth: 2000,
fullHeight: 990, // KO
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 0,
croppedY: 500,
}), {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 0,
croppedY: 500,
} satisfies PanoData);
});

it('should fix invalid croppedY', () => {
assertDeepEqualLenient(mergePanoData(2000, 500, {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 0,
croppedY: 1000, // KO
}), {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 0,
croppedY: 500,
} satisfies PanoData);

assertDeepEqualLenient(mergePanoData(2000, 500, {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 0,
croppedY: -500, // KO
}), {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 2000,
croppedHeight: 500,
croppedX: 0,
croppedY: 0,
} satisfies PanoData);
});

it('should fix invalid croppedX', () => {
assertDeepEqualLenient(mergePanoData(1000, 1000, {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 1000,
croppedHeight: 1000,
croppedX: 1500, // KO
croppedY: 0,
}), {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 1000,
croppedHeight: 1000,
croppedX: 1000,
croppedY: 0,
} satisfies PanoData);

assertDeepEqualLenient(mergePanoData(1000, 1000, {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 1000,
croppedHeight: 1000,
croppedX: -500, // KO
croppedY: 0,
}), {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 1000,
croppedHeight: 1000,
croppedX: 0,
croppedY: 0,
} satisfies PanoData);
});

it('should complete missing fullHeight', () => {
assertDeepEqualLenient(mergePanoData(1000, 1000, {
fullWidth: 2000,
croppedX: 500,
croppedY: 0,
}), {
fullWidth: 2000,
fullHeight: 1000,
croppedWidth: 1000,
croppedHeight: 1000,
croppedX: 500,
croppedY: 0,
});
});
});

function assertDeepEqualLenient(actual: any, expected: any) {
const picked = {} as any;
Object.keys(expected).forEach(key => {
picked[key] = actual[key];
});
assert.deepStrictEqual(picked, expected);
}
Loading

0 comments on commit 98cf4d3

Please sign in to comment.