Skip to content

Commit 03bb1d0

Browse files
authored
Add new Quaternion nodes to flow graph and glTF interactivity (#16739)
See KhronosGroup/glTF@b8e8a5a for the glTF interactivity spec updates.
1 parent 1b42d92 commit 03bb1d0

File tree

9 files changed

+506
-46
lines changed

9 files changed

+506
-46
lines changed

packages/dev/core/src/FlowGraph/Blocks/Data/Math/flowGraphMathBlocks.ts

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { FlowGraphDataConnection } from "core/FlowGraph/flowGraphDataConnec
1212
import type { FlowGraphContext } from "core/FlowGraph/flowGraphContext";
1313
import { FlowGraphMatrix2D, FlowGraphMatrix3D } from "core/FlowGraph/CustomTypes/flowGraphMatrix";
1414
import type { FlowGraphMathOperationType, FlowGraphNumber } from "core/FlowGraph/utils";
15-
import { _AreSameIntegerClass, _AreSameMatrixClass, _AreSameVectorClass, _GetClassNameOf, getNumericValue, isNumeric } from "core/FlowGraph/utils";
15+
import { _AreSameIntegerClass, _AreSameMatrixClass, _AreSameVectorOrQuaternionClass, _GetClassNameOf, getNumericValue, isNumeric } from "core/FlowGraph/utils";
1616

1717
/**
1818
* A configuration interface for math blocks
@@ -58,12 +58,9 @@ export class FlowGraphAddBlock extends FlowGraphBinaryOperationBlock<FlowGraphMa
5858
private _polymorphicAdd(a: FlowGraphMathOperationType, b: FlowGraphMathOperationType) {
5959
const aClassName = _GetClassNameOf(a);
6060
const bClassName = _GetClassNameOf(b);
61-
if (_AreSameVectorClass(aClassName, bClassName) || _AreSameMatrixClass(aClassName, bClassName) || _AreSameIntegerClass(aClassName, bClassName)) {
61+
if (_AreSameVectorOrQuaternionClass(aClassName, bClassName) || _AreSameMatrixClass(aClassName, bClassName) || _AreSameIntegerClass(aClassName, bClassName)) {
6262
// cast to vector3, but any other cast will be fine
6363
return (a as Vector3).add(b as Vector3);
64-
} else if (aClassName === FlowGraphTypes.Quaternion || bClassName === FlowGraphTypes.Quaternion) {
65-
// this is a simple add, and should be also supported between Quat and Vector4. Therefore -
66-
return (a as Quaternion).add(b as Quaternion);
6764
} else {
6865
// at this point at least one of the variables is a number.
6966
if (this.config?.preventIntegerFloatArithmetic && typeof a !== typeof b) {
@@ -97,11 +94,9 @@ export class FlowGraphSubtractBlock extends FlowGraphBinaryOperationBlock<FlowGr
9794
private _polymorphicSubtract(a: FlowGraphMathOperationType, b: FlowGraphMathOperationType) {
9895
const aClassName = _GetClassNameOf(a);
9996
const bClassName = _GetClassNameOf(b);
100-
if (_AreSameVectorClass(aClassName, bClassName) || _AreSameIntegerClass(aClassName, bClassName) || _AreSameMatrixClass(aClassName, bClassName)) {
97+
if (_AreSameVectorOrQuaternionClass(aClassName, bClassName) || _AreSameIntegerClass(aClassName, bClassName) || _AreSameMatrixClass(aClassName, bClassName)) {
98+
// cast to vector3, but it can be casted to any vector type
10199
return (a as Vector3).subtract(b as Vector3);
102-
} else if (aClassName === FlowGraphTypes.Quaternion || bClassName === FlowGraphTypes.Quaternion) {
103-
// this is a simple subtract, and should be also supported between Quat and Vector4. Therefore -
104-
return (a as Quaternion).subtract(b as Quaternion);
105100
} else {
106101
// at this point at least one of the variables is a number.
107102
if (this.config?.preventIntegerFloatArithmetic && typeof a !== typeof b) {
@@ -132,16 +127,9 @@ export class FlowGraphMultiplyBlock extends FlowGraphBinaryOperationBlock<FlowGr
132127
private _polymorphicMultiply(a: FlowGraphMathOperationType, b: FlowGraphMathOperationType) {
133128
const aClassName = _GetClassNameOf(a);
134129
const bClassName = _GetClassNameOf(b);
135-
if (_AreSameVectorClass(aClassName, bClassName) || _AreSameIntegerClass(aClassName, bClassName)) {
130+
if (_AreSameVectorOrQuaternionClass(aClassName, bClassName) || _AreSameIntegerClass(aClassName, bClassName)) {
131+
// cast to vector3, but it can be casted to any vector type
136132
return (a as Vector3).multiply(b as Vector3);
137-
} else if (aClassName === FlowGraphTypes.Quaternion || bClassName === FlowGraphTypes.Quaternion) {
138-
// this is a simple multiply (per component!), and should be also supported between Quat and Vector4. Therefore -
139-
const aClone = (a as Quaternion).clone();
140-
aClone.x *= (b as Quaternion).x;
141-
aClone.y *= (b as Quaternion).y;
142-
aClone.z *= (b as Quaternion).z;
143-
aClone.w *= (b as Quaternion).w;
144-
return aClone;
145133
} else if (_AreSameMatrixClass(aClassName, bClassName)) {
146134
if (this.config?.useMatrixPerComponent) {
147135
// this is the definition of multiplication of glTF interactivity
@@ -195,7 +183,7 @@ export class FlowGraphDivideBlock extends FlowGraphBinaryOperationBlock<FlowGrap
195183
private _polymorphicDivide(a: FlowGraphMathOperationType, b: FlowGraphMathOperationType) {
196184
const aClassName = _GetClassNameOf(a);
197185
const bClassName = _GetClassNameOf(b);
198-
if (_AreSameVectorClass(aClassName, bClassName) || _AreSameIntegerClass(aClassName, bClassName)) {
186+
if (_AreSameVectorOrQuaternionClass(aClassName, bClassName) || _AreSameIntegerClass(aClassName, bClassName)) {
199187
// cast to vector3, but it can be casted to any vector type
200188
return (a as Vector3).divide(b as Vector3);
201189
} else if (aClassName === FlowGraphTypes.Quaternion || bClassName === FlowGraphTypes.Quaternion) {
@@ -710,7 +698,7 @@ export class FlowGraphEqualityBlock extends FlowGraphBinaryOperationBlock<FlowGr
710698
if (typeof a !== typeof b) {
711699
return false;
712700
}
713-
if (_AreSameVectorClass(aClassName, bClassName) || _AreSameMatrixClass(aClassName, bClassName) || _AreSameIntegerClass(aClassName, bClassName)) {
701+
if (_AreSameVectorOrQuaternionClass(aClassName, bClassName) || _AreSameMatrixClass(aClassName, bClassName) || _AreSameIntegerClass(aClassName, bClassName)) {
714702
return (a as Vector3).equals(b as Vector3);
715703
} else {
716704
return a === b;

packages/dev/core/src/FlowGraph/Blocks/Data/Math/flowGraphVectorMathBlocks.ts

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { IFlowGraphBlockConfiguration } from "core/FlowGraph/flowGraphBlock";
1+
import { FlowGraphBlock, type IFlowGraphBlockConfiguration } from "core/FlowGraph/flowGraphBlock";
22
import {
33
RichTypeVector3,
44
FlowGraphTypes,
@@ -8,16 +8,25 @@ import {
88
RichTypeMatrix,
99
getRichTypeByFlowGraphType,
1010
RichTypeQuaternion,
11+
RichTypeBoolean,
1112
} from "core/FlowGraph/flowGraphRichTypes";
1213
import { RegisterClass } from "core/Misc/typeStore";
1314
import { FlowGraphBlockNames } from "../../flowGraphBlockNames";
1415
import { FlowGraphBinaryOperationBlock } from "../flowGraphBinaryOperationBlock";
1516
import { FlowGraphUnaryOperationBlock } from "../flowGraphUnaryOperationBlock";
16-
import { Vector3, Vector4 } from "core/Maths/math.vector";
17-
import type { Matrix, Quaternion, Vector2 } from "core/Maths/math.vector";
17+
import { Quaternion, Vector3, Vector4 } from "core/Maths/math.vector";
18+
import type { Matrix, Vector2 } from "core/Maths/math.vector";
1819
import type { FlowGraphMatrix2D, FlowGraphMatrix3D } from "core/FlowGraph/CustomTypes";
1920
import type { FlowGraphMatrix, FlowGraphVector } from "core/FlowGraph/utils";
2021
import { _GetClassNameOf } from "core/FlowGraph/utils";
22+
import type { FlowGraphDataConnection } from "../../../flowGraphDataConnection";
23+
import type { FlowGraphContext } from "../../../flowGraphContext";
24+
import { GetAngleBetweenQuaternions, GetQuaternionFromDirections } from "../../../../Maths/math.vector.functions";
25+
import type { Nullable } from "../../../../types";
26+
27+
const AxisCacheName = "cachedOperationAxis";
28+
const AngleCacheName = "cachedOperationAngle";
29+
const CacheExecIdName = "cachedExecutionId";
2130

2231
/**
2332
* Vector length block.
@@ -200,3 +209,111 @@ export class FlowGraphTransformCoordinatesBlock extends FlowGraphBinaryOperation
200209
}
201210

202211
RegisterClass(FlowGraphBlockNames.TransformCoordinates, FlowGraphTransformCoordinatesBlock);
212+
213+
/**
214+
* Conjugate the quaternion.
215+
*/
216+
export class FlowGraphConjugateBlock extends FlowGraphUnaryOperationBlock<Quaternion, Quaternion> {
217+
constructor(config?: IFlowGraphBlockConfiguration) {
218+
super(RichTypeQuaternion, RichTypeQuaternion, (a) => a.conjugate(), FlowGraphBlockNames.Conjugate, config);
219+
}
220+
}
221+
222+
RegisterClass(FlowGraphBlockNames.Conjugate, FlowGraphConjugateBlock);
223+
224+
/**
225+
* Get the angle between two quaternions.
226+
*/
227+
export class FlowGraphAngleBetweenBlock extends FlowGraphBinaryOperationBlock<Quaternion, Quaternion, number> {
228+
constructor(config?: IFlowGraphBlockConfiguration) {
229+
super(RichTypeQuaternion, RichTypeQuaternion, RichTypeNumber, (a, b) => GetAngleBetweenQuaternions(a, b), FlowGraphBlockNames.AngleBetween, config);
230+
}
231+
}
232+
233+
RegisterClass(FlowGraphBlockNames.AngleBetween, FlowGraphAngleBetweenBlock);
234+
235+
/**
236+
* Get the quaternion from an axis and an angle.
237+
*/
238+
export class FlowGraphQuaternionFromAxisAngleBlock extends FlowGraphBinaryOperationBlock<Vector3, number, Quaternion> {
239+
constructor(config?: IFlowGraphBlockConfiguration) {
240+
super(RichTypeVector3, RichTypeNumber, RichTypeQuaternion, (a, b) => Quaternion.RotationAxis(a, b), FlowGraphBlockNames.QuaternionFromAxisAngle, config);
241+
}
242+
}
243+
244+
RegisterClass(FlowGraphBlockNames.QuaternionFromAxisAngle, FlowGraphQuaternionFromAxisAngleBlock);
245+
246+
/**
247+
* Get the axis and angle from a quaternion.
248+
*/
249+
export class FlowGraphAxisAngleFromQuaternionBlock extends FlowGraphBlock {
250+
/**
251+
* The input of this block.
252+
*/
253+
public readonly a: FlowGraphDataConnection<Quaternion>;
254+
255+
/**
256+
* The output axis of rotation.
257+
*/
258+
public readonly axis: FlowGraphDataConnection<Vector3>;
259+
260+
/**
261+
* The output angle of rotation.
262+
*/
263+
public readonly angle: FlowGraphDataConnection<number>;
264+
265+
/**
266+
* Output connection: Whether the value is valid.
267+
*/
268+
public readonly isValid: FlowGraphDataConnection<boolean>;
269+
270+
constructor(config?: IFlowGraphBlockConfiguration) {
271+
super(config);
272+
273+
this.a = this.registerDataInput("a", RichTypeQuaternion);
274+
275+
this.axis = this.registerDataOutput("axis", RichTypeVector3);
276+
this.angle = this.registerDataOutput("angle", RichTypeNumber);
277+
278+
this.isValid = this.registerDataOutput("isValid", RichTypeBoolean);
279+
}
280+
281+
/** @override */
282+
public override _updateOutputs(context: FlowGraphContext) {
283+
const cachedExecutionId = context._getExecutionVariable(this, CacheExecIdName, -1);
284+
const cachedAxis = context._getExecutionVariable<Nullable<Vector3>>(this, AxisCacheName, null);
285+
const cachedAngle = context._getExecutionVariable<Nullable<number>>(this, AngleCacheName, null);
286+
if (cachedAxis !== undefined && cachedAxis !== null && cachedAngle !== undefined && cachedAngle !== null && cachedExecutionId === context.executionId) {
287+
this.axis.setValue(cachedAxis, context);
288+
this.angle.setValue(cachedAngle, context);
289+
} else {
290+
try {
291+
const { axis, angle } = this.a.getValue(context).toAxisAngle();
292+
context._setExecutionVariable(this, AxisCacheName, axis);
293+
context._setExecutionVariable(this, AngleCacheName, angle);
294+
context._setExecutionVariable(this, CacheExecIdName, context.executionId);
295+
this.axis.setValue(axis, context);
296+
this.angle.setValue(angle, context);
297+
this.isValid.setValue(true, context);
298+
} catch (e) {
299+
this.isValid.setValue(false, context);
300+
}
301+
}
302+
}
303+
304+
/** @override */
305+
public override getClassName(): string {
306+
return FlowGraphBlockNames.AxisAngleFromQuaternion;
307+
}
308+
}
309+
310+
RegisterClass(FlowGraphBlockNames.AxisAngleFromQuaternion, FlowGraphAxisAngleFromQuaternionBlock);
311+
312+
/**
313+
* Get the quaternion from two direction vectors.
314+
*/
315+
export class FlowGraphQuaternionFromDirectionsBlock extends FlowGraphBinaryOperationBlock<Vector3, Vector3, Quaternion> {
316+
constructor(config?: IFlowGraphBlockConfiguration) {
317+
super(RichTypeVector3, RichTypeVector3, RichTypeQuaternion, (a, b) => GetQuaternionFromDirections(a, b), FlowGraphBlockNames.QuaternionFromDirections, config);
318+
}
319+
}

packages/dev/core/src/FlowGraph/Blocks/flowGraphBlockFactory.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,16 @@ export function blockFactory(blockName: FlowGraphBlockNames | string): () => Pro
254254
return async () => (await import("./Data/Math/flowGraphVectorMathBlocks")).FlowGraphTransformBlock;
255255
case FlowGraphBlockNames.TransformCoordinates:
256256
return async () => (await import("./Data/Math/flowGraphVectorMathBlocks")).FlowGraphTransformCoordinatesBlock;
257+
case FlowGraphBlockNames.Conjugate:
258+
return async () => (await import("./Data/Math/flowGraphVectorMathBlocks")).FlowGraphConjugateBlock;
259+
case FlowGraphBlockNames.AngleBetween:
260+
return async () => (await import("./Data/Math/flowGraphVectorMathBlocks")).FlowGraphAngleBetweenBlock;
261+
case FlowGraphBlockNames.QuaternionFromAxisAngle:
262+
return async () => (await import("./Data/Math/flowGraphVectorMathBlocks")).FlowGraphQuaternionFromAxisAngleBlock;
263+
case FlowGraphBlockNames.AxisAngleFromQuaternion:
264+
return async () => (await import("./Data/Math/flowGraphVectorMathBlocks")).FlowGraphAxisAngleFromQuaternionBlock;
265+
case FlowGraphBlockNames.QuaternionFromDirections:
266+
return async () => (await import("./Data/Math/flowGraphVectorMathBlocks")).FlowGraphQuaternionFromDirectionsBlock;
257267
case FlowGraphBlockNames.MatrixDecompose:
258268
return async () => (await import("./Data/Math/flowGraphMatrixMathBlocks")).FlowGraphMatrixDecomposeBlock;
259269
case FlowGraphBlockNames.MatrixCompose:

packages/dev/core/src/FlowGraph/Blocks/flowGraphBlockNames.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ export const enum FlowGraphBlockNames {
127127
ExtractMatrix3D = "FlowGraphExtractMatrix3DBlock",
128128
TransformVector = "FlowGraphTransformVectorBlock",
129129
TransformCoordinates = "FlowGraphTransformCoordinatesBlock",
130+
Conjugate = "FlowGraphConjugateBlock",
131+
AngleBetween = "FlowGraphAngleBetweenBlock",
132+
QuaternionFromAxisAngle = "FlowGraphQuaternionFromAxisAngleBlock",
133+
AxisAngleFromQuaternion = "FlowGraphAxisAngleFromQuaternionBlock",
134+
QuaternionFromDirections = "FlowGraphQuaternionFromDirectionsBlock",
130135
MatrixDecompose = "FlowGraphMatrixDecompose",
131136
MatrixCompose = "FlowGraphMatrixCompose",
132137
BooleanToFloat = "FlowGraphBooleanToFloat",

packages/dev/core/src/FlowGraph/utils.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ export function _GetClassNameOf(v: any) {
3232

3333
/**
3434
* @internal
35-
* Check if two classname are the same and are vector classes.
35+
* Check if two classname are the same and are vector or quaternion classes.
3636
* @param className the first class name
3737
* @param className2 the second class name
38-
* @returns whether the two class names are the same and are vector classes.
38+
* @returns whether the two class names are the same and are vector or quaternion classes.
3939
*/
40-
export function _AreSameVectorClass(className: string, className2: string) {
41-
return className === className2 && (className === FlowGraphTypes.Vector2 || className === FlowGraphTypes.Vector3 || className === FlowGraphTypes.Vector4);
40+
export function _AreSameVectorOrQuaternionClass(className: string, className2: string) {
41+
return (
42+
className === className2 &&
43+
(className === FlowGraphTypes.Vector2 || className === FlowGraphTypes.Vector3 || className === FlowGraphTypes.Vector4 || className === FlowGraphTypes.Quaternion)
44+
);
4245
}
4346

4447
/**

packages/dev/core/src/Maths/math.vector.functions.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { Vector2, Vector3, Vector4 } from "./math.vector";
1+
import type { DeepImmutable } from "../types";
2+
import { Clamp } from "./math.scalar.functions";
3+
import type { Vector2, Vector4 } from "./math.vector";
4+
import { Quaternion, Vector3 } from "./math.vector";
25

36
/**
47
* Creates a string representation of the Vector2
@@ -29,3 +32,39 @@ export function Vector3ToFixed(vector: Vector3, decimalCount: number): string {
2932
export function Vector4ToFixed(vector: Vector4, decimalCount: number): string {
3033
return `{X: ${vector.x.toFixed(decimalCount)} Y: ${vector.y.toFixed(decimalCount)} Z: ${vector.z.toFixed(decimalCount)} W: ${vector.w.toFixed(decimalCount)}}`;
3134
}
35+
36+
/**
37+
* Returns the angle in radians between two quaternions
38+
* @param q1 defines the first quaternion
39+
* @param q2 defines the second quaternion
40+
* @returns the angle in radians between the two quaternions
41+
*/
42+
export function GetAngleBetweenQuaternions(q1: DeepImmutable<Quaternion>, q2: DeepImmutable<Quaternion>): number {
43+
return Math.acos(Clamp(Quaternion.Dot(q1, q2))) * 2;
44+
}
45+
46+
/**
47+
* Creates a quaternion from two direction vectors
48+
* @param a defines the first direction vector
49+
* @param b defines the second direction vector
50+
* @returns the target quaternion
51+
*/
52+
export function GetQuaternionFromDirections<T extends Vector3>(a: T, b: T): Quaternion {
53+
const result = new Quaternion();
54+
GetQuaternionFromDirectionsToRef(a, b, result);
55+
return result;
56+
}
57+
58+
/**
59+
* Creates a quaternion from two direction vectors
60+
* @param a defines the first direction vector
61+
* @param b defines the second direction vector
62+
* @param result defines the target quaternion
63+
* @returns the target quaternion
64+
*/
65+
export function GetQuaternionFromDirectionsToRef<T extends Vector3, ResultT extends Quaternion>(a: T, b: T, result: ResultT): ResultT {
66+
const axis = Vector3.Cross(a, b);
67+
const angle = Math.acos(Clamp(Vector3.Dot(a, b), -1, 1));
68+
Quaternion.RotationAxisToRef(axis, angle, result);
69+
return result;
70+
}

packages/dev/core/src/Maths/math.vector.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5311,6 +5311,37 @@ export class Quaternion implements Tensor<Tuple<number, 4>, Quaternion>, IQuater
53115311
return this._x * other._x + this._y * other._y + this._z * other._z + this._w * other._w;
53125312
}
53135313

5314+
/**
5315+
* Converts the current quaternion to an axis angle representation
5316+
* @returns the axis and angle in radians
5317+
*/
5318+
public toAxisAngle(): { axis: Vector3; angle: number } {
5319+
const axis = Vector3.Zero();
5320+
const angle = this.toAxisAngleToRef(axis);
5321+
return { axis, angle };
5322+
}
5323+
5324+
/**
5325+
* Converts the current quaternion to an axis angle representation
5326+
* @param axis defines the target axis vector
5327+
* @returns the angle in radians
5328+
*/
5329+
public toAxisAngleToRef<T extends Vector3>(axis: T): number {
5330+
let angle = 0;
5331+
const sinHalfAngle = Math.sqrt(this._x * this._x + this._y * this._y + this._z * this._z);
5332+
const cosHalfAngle = this._w;
5333+
5334+
if (sinHalfAngle > 0) {
5335+
angle = 2 * Math.atan2(sinHalfAngle, cosHalfAngle);
5336+
axis.set(this._x / sinHalfAngle, this._y / sinHalfAngle, this._z / sinHalfAngle);
5337+
} else {
5338+
angle = 0;
5339+
axis.set(1, 0, 0);
5340+
}
5341+
5342+
return angle;
5343+
}
5344+
53145345
// Statics
53155346

53165347
/**

0 commit comments

Comments
 (0)