Skip to content
This repository was archived by the owner on Apr 18, 2024. It is now read-only.

Commit ee6ed69

Browse files
authored
fix: LSDV-4776: Fix interpolating a video region rotation prop (#1252)
* fix: LSDV-4776: Fix interpolating a video region rotation prop * Fix eslint problems * fix eslint * Avoid bad practice of reassigning arguments values * Add some comments about rotate interpolation * Provide some examples for interpolateProp in documentation comments
1 parent edc6da5 commit ee6ed69

File tree

4 files changed

+151
-8
lines changed

4 files changed

+151
-8
lines changed

src/regions/VideoRectangleRegion.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import NormalizationMixin from '../mixins/Normalization';
44
import RegionsMixin from '../mixins/Regions';
55
import Registry from '../core/Registry';
66
import { AreaMixin } from '../mixins/AreaMixin';
7-
import { interpolateProp, onlyProps, VideoRegion } from './VideoRegion';
7+
import { onlyProps, VideoRegion } from './VideoRegion';
8+
import { interpolateProp } from '../utils/props';
89

910
const Model = types
1011
.model('VideoRectangleRegionModel', {

src/regions/VideoRegion.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@ import { VideoModel } from '../tags/object/Video';
66
import { guidGenerator } from '../core/Helpers';
77
import { AreaMixin } from '../mixins/AreaMixin';
88

9-
export const interpolateProp = (start, end, frame, prop) => {
10-
// @todo edge cases
11-
const r = (frame - start.frame) / (end.frame - start.frame);
12-
13-
return start[prop] + (end[prop] - start[prop]) * r;
14-
};
15-
169
export const onlyProps = (props, obj) => {
1710
return Object.fromEntries(props.map(prop => [
1811
prop,

src/utils/__tests__/props.test.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/* global describe, expect, it */
2+
3+
import { interpolateProp, normalizeAngle } from '../props';
4+
5+
describe('normalizeAngle', () => {
6+
it('returns the same value for angle in the interval (-180; 180]', () => {
7+
expect(normalizeAngle(-179.8)).toBe(-179.8);
8+
expect(normalizeAngle(-42)).toBe(-42);
9+
expect(normalizeAngle(0)).toBe(0);
10+
expect(normalizeAngle(90)).toBe(90);
11+
expect(normalizeAngle(180)).toBe(180);
12+
});
13+
it('projects angles from the intervals [-360;-180] and (180;360] onto the interval (-180; 180]', () => {
14+
expect(normalizeAngle(-360)).toBe(0);
15+
expect(normalizeAngle(-181)).toBe(179);
16+
expect(normalizeAngle(220)).toBe(-140);
17+
expect(normalizeAngle(359)).toBe(-1);
18+
});
19+
});
20+
21+
describe('interpolateProp', () => {
22+
it('returns the exact value from the object containing the given frame', () => {
23+
const objA = {
24+
x: 0,
25+
y: -50,
26+
size: 100,
27+
frame: 1,
28+
};
29+
const objB = {
30+
x: 700,
31+
y: -500,
32+
size: 1,
33+
frame: 100,
34+
};
35+
36+
for (const prop of Object.keys(objA)) {
37+
expect(interpolateProp(objA, objB, objA.frame, prop)).toBe(objA[prop]);
38+
expect(interpolateProp(objA, objB, objB.frame, prop)).toBe(objB[prop]);
39+
}
40+
});
41+
it('linearly interpolates numeric properties between frames', () => {
42+
const objA = {
43+
x: -10,
44+
y: -50,
45+
size: 3,
46+
frame: 0,
47+
};
48+
const objB = {
49+
x: 10,
50+
y: -500,
51+
size: 4,
52+
frame: 100,
53+
};
54+
55+
expect(interpolateProp(objA, objB, 25, 'x')).toBe(-5);
56+
expect(interpolateProp(objA, objB, 50, 'x')).toBe(0);
57+
expect(interpolateProp(objA, objB, 75, 'x')).toBe(5);
58+
expect(interpolateProp(objA, objB, 8, 'y')).toBe(-86);
59+
expect(interpolateProp(objA, objB, 30, 'y')).toBe(-185);
60+
expect(interpolateProp(objA, objB, 72, 'y')).toBe(-374);
61+
expect(interpolateProp(objA, objB, 50, 'size')).toBe(3.5);
62+
expect(interpolateProp(objA, objB, 88, 'size')).toBe(3.88);
63+
expect(interpolateProp(objA, objB, 3, 'size')).toBe(3.03);
64+
});
65+
it('linearly interpolates rotation in the shortest way', () => {
66+
const objA = {
67+
rotation: -170,
68+
frame: 0,
69+
};
70+
const objB = {
71+
rotation: 170,
72+
frame: 20,
73+
};
74+
75+
expect(interpolateProp(objA, objB, 0, 'rotation')).toBe(-170);
76+
expect(interpolateProp(objA, objB, 5, 'rotation')).toBe(-175);
77+
expect(interpolateProp(objA, objB, 10, 'rotation')).toBe(180);
78+
expect(interpolateProp(objA, objB, 15, 'rotation')).toBe(175);
79+
expect(interpolateProp(objA, objB, 20, 'rotation')).toBe(170);
80+
81+
expect(interpolateProp(objB, objA, 15, 'rotation')).toBe(175);
82+
83+
const objE = {
84+
rotation: 180,
85+
frame: 0,
86+
};
87+
const objN = {
88+
rotation: 90,
89+
frame: 10,
90+
};
91+
const objW = {
92+
rotation: 0,
93+
frame: 20,
94+
};
95+
const objS = {
96+
rotation: -90,
97+
frame: 30,
98+
};
99+
100+
expect(interpolateProp(objE, objN, 5, 'rotation')).toBe(135);
101+
expect(interpolateProp(objN, objW, 15, 'rotation')).toBe(45);
102+
expect(interpolateProp(objW, objS, 25, 'rotation')).toBe(-45);
103+
expect(interpolateProp(objS, objE, 15, 'rotation')).toBe(-135);
104+
});
105+
});

src/utils/props.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* project any angle onto the interval (-180;180]
3+
*/
4+
export function normalizeAngle(angle: number) {
5+
let a = angle;
6+
7+
while (a > 0) a -= 360;
8+
return (a - 180) % 360 + 180;
9+
}
10+
11+
type SequenceItem = {
12+
frame: number,
13+
[k: string]: any,
14+
}
15+
16+
/**
17+
* interpolate prop between two sequence items
18+
* @return {any} propValue
19+
* @example
20+
* interpolateProp({frame: 0, x: -10}, {frame: 100, x: 10}, 25, 'x'); // will return -5
21+
* @example
22+
* interpolateProp(
23+
* {frame: 0, rotation: -170},
24+
* {frame: 20, rotation: 170},
25+
* 5,
26+
* 'rotation'
27+
* ); // will return -175
28+
*/
29+
export const interpolateProp = (start: SequenceItem, end: SequenceItem, frame: number, prop: string): any => {
30+
// @todo edge cases
31+
const r = (frame - start.frame) / (end.frame - start.frame);
32+
33+
// Interpolation of angles is more tricky due to the cyclical nature of the angle value.
34+
if (prop === 'rotation') {
35+
// In order to perform interpolation over the shortest distance,
36+
// we must normalize the difference in the values of the angles to the interval of [180;180) degrees,
37+
// because this is analogous to [0;360) degrees,
38+
// but at the same time the maximum absolute value remains the minimum possible 180 degrees.
39+
const dAngle = normalizeAngle(end[prop] - start[prop]);
40+
41+
return normalizeAngle(start[prop] + dAngle * r);
42+
}
43+
return start[prop] + (end[prop] - start[prop]) * r;
44+
};

0 commit comments

Comments
 (0)