Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly support global matrix in glTF interactivity #16285

Merged
merged 3 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { FlowGraphAssetType } from "core/FlowGraph/flowGraphAssetsContext";
import type { IFlowGraphBlockConfiguration } from "core/FlowGraph/flowGraphBlock";
import { FlowGraphBlock } from "core/FlowGraph/flowGraphBlock";
import type { FlowGraphContext } from "core/FlowGraph/flowGraphContext";
import type { FlowGraphDataConnection } from "core/FlowGraph/flowGraphDataConnection";
import { FlowGraphPathConverterComponent } from "core/FlowGraph/flowGraphPathConverterComponent";
Expand All @@ -13,6 +12,7 @@ import type { Animation } from "core/Animations/animation";
import type { EasingFunction } from "core/Animations/easing";
import type { Vector4 } from "core/Maths/math.vector";
import { Color3, Color4 } from "core/Maths/math.color";
import { FlowGraphCachedOperationBlock } from "../flowGraphCachedOperationBlock";

/**
* Configuration for the JSON pointer parser block.
Expand All @@ -38,7 +38,7 @@ export interface IFlowGraphJsonPointerParserBlockConfiguration extends IFlowGrap
* The output is an object and a property name.
* Optionally, the block can also output the value of the property. This is configurable.
*/
export class FlowGraphJsonPointerParserBlock<P extends any, O extends FlowGraphAssetType> extends FlowGraphBlock {
export class FlowGraphJsonPointerParserBlock<P extends any, O extends FlowGraphAssetType> extends FlowGraphCachedOperationBlock<P> {
/**
* Output connection: The object that contains the property.
*/
Expand All @@ -49,12 +49,6 @@ export class FlowGraphJsonPointerParserBlock<P extends any, O extends FlowGraphA
*/
public readonly propertyName: FlowGraphDataConnection<string>;

/**
* Output connection: The value of the property.
* Note that per default this is not outputted. It can be enabled by setting the outputValue property to true.
*/
public readonly value: FlowGraphDataConnection<P>;

/**
* Output connection: A function that can be used to update the value of the property.
*/
Expand All @@ -70,11 +64,6 @@ export class FlowGraphJsonPointerParserBlock<P extends any, O extends FlowGraphA
*/
public readonly generateAnimationsFunction: FlowGraphDataConnection<() => (keys: any[], fps: number, easingFunction?: EasingFunction) => Animation[]>;

/**
* Output connection: Whether the value is valid.
*/
public readonly isValid: FlowGraphDataConnection<boolean>;

/**
* The component with the templated inputs for the provided path.
*/
Expand All @@ -86,47 +75,29 @@ export class FlowGraphJsonPointerParserBlock<P extends any, O extends FlowGraphA
*/
public override config: IFlowGraphJsonPointerParserBlockConfiguration
) {
super(config);
super(RichTypeAny, config);
this.object = this.registerDataOutput("object", RichTypeAny);
this.propertyName = this.registerDataOutput("propertyName", RichTypeAny);
this.isValid = this.registerDataOutput("isValid", RichTypeAny);
if (config.outputValue) {
this.value = this.registerDataOutput("value", RichTypeAny);
}
this.setterFunction = this.registerDataOutput("setFunction", RichTypeAny, this._setPropertyValue.bind(this));
this.getterFunction = this.registerDataOutput("getFunction", RichTypeAny, this._getPropertyValue.bind(this));
this.generateAnimationsFunction = this.registerDataOutput("generateAnimationsFunction", RichTypeAny, this._getInterpolationAnimationPropertyInfo.bind(this));
this.templateComponent = new FlowGraphPathConverterComponent(config.jsonPointer, this);
}

public override _updateOutputs(context: FlowGraphContext) {
try {
const accessorContainer = this.templateComponent.getAccessor(this.config.pathConverter, context);
const value = accessorContainer.info.get(accessorContainer.object);
const object = accessorContainer.info.getTarget?.(accessorContainer.object);
const propertyName = accessorContainer.info.getPropertyName?.[0](accessorContainer.object);
if (!object) {
this.isValid.setValue(false, context);
return;
} else {
this.object.setValue(object, context);
if (propertyName) {
this.propertyName.setValue(propertyName, context);
}
this.isValid.setValue(true, context);
}
if (this.config.outputValue) {
if (value === undefined) {
this.isValid.setValue(false, context);
} else {
this.value.setValue(value, context);
this.isValid.setValue(true, context);
}
public override _doOperation(context: FlowGraphContext): P {
const accessorContainer = this.templateComponent.getAccessor(this.config.pathConverter, context);
const value = accessorContainer.info.get(accessorContainer.object) as P;
const object = accessorContainer.info.getTarget?.(accessorContainer.object);
const propertyName = accessorContainer.info.getPropertyName?.[0](accessorContainer.object);
if (!object) {
throw new Error("Object is undefined");
} else {
this.object.setValue(object, context);
if (propertyName) {
this.propertyName.setValue(propertyName, context);
}
} catch (e) {
this.isValid.setValue(false, context);
return;
}
return value;
}

private _setPropertyValue(_target: O, _propertyName: string, value: P, context: FlowGraphContext): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FlowGraphBlock } from "../../flowGraphBlock";
import type { FlowGraphContext } from "../../flowGraphContext";
import type { FlowGraphDataConnection } from "../../flowGraphDataConnection";
import type { RichType } from "../../flowGraphRichTypes";
import { RichTypeBoolean } from "../../flowGraphRichTypes";

const cacheName = "cachedOperationValue";
const cacheExecIdName = "cachedExecutionId";
Expand All @@ -17,29 +18,45 @@ export abstract class FlowGraphCachedOperationBlock<OutputT> extends FlowGraphBl
*/
public readonly value: FlowGraphDataConnection<OutputT>;

/**
* Output connection: Whether the value is valid.
*/
public readonly isValid: FlowGraphDataConnection<boolean>;

constructor(outputRichType: RichType<OutputT>, config?: IFlowGraphBlockConfiguration) {
super(config);

this.value = this.registerDataOutput("value", outputRichType);
this.isValid = this.registerDataOutput("isValid", RichTypeBoolean);
}

/**
* @internal
* Operation to realize
* @param context the graph context
*/
public abstract _doOperation(context: FlowGraphContext): OutputT;
public abstract _doOperation(context: FlowGraphContext): OutputT | undefined;

public override _updateOutputs(context: FlowGraphContext) {
const cachedExecutionId = context._getExecutionVariable(this, cacheExecIdName, -1);
const cachedValue = context._getExecutionVariable<Nullable<OutputT>>(this, cacheName, null);
if (cachedValue !== undefined && cachedValue !== null && cachedExecutionId === context.executionId) {
this.isValid.setValue(true, context);
this.value.setValue(cachedValue, context);
} else {
const calculatedValue = this._doOperation(context);
context._setExecutionVariable(this, cacheName, calculatedValue);
context._setExecutionVariable(this, cacheExecIdName, context.executionId);
this.value.setValue(calculatedValue, context);
try {
const calculatedValue = this._doOperation(context);
if (calculatedValue === undefined || calculatedValue === null) {
this.isValid.setValue(false, context);
return;
}
context._setExecutionVariable(this, cacheName, calculatedValue);
context._setExecutionVariable(this, cacheExecIdName, context.executionId);
this.value.setValue(calculatedValue, context);
this.isValid.setValue(true, context);
} catch (e) {
this.isValid.setValue(false, context);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { AssetType, FlowGraphAssetType } from "core/FlowGraph/flowGraphAssetsContext";
import type { IFlowGraphBlockConfiguration } from "core/FlowGraph/flowGraphBlock";
import { FlowGraphBlock } from "core/FlowGraph/flowGraphBlock";
import type { FlowGraphContext } from "core/FlowGraph/flowGraphContext";
import type { FlowGraphDataConnection } from "core/FlowGraph/flowGraphDataConnection";
import { RichTypeAny } from "core/FlowGraph/flowGraphRichTypes";
import { RegisterClass } from "core/Misc/typeStore";
import { FlowGraphBlockNames } from "../flowGraphBlockNames";
import { FlowGraphCachedOperationBlock } from "./flowGraphCachedOperationBlock";

export interface IFlowGraphGetPropertyBlockConfiguration<O extends FlowGraphAssetType> extends IFlowGraphBlockConfiguration {
/**
Expand All @@ -32,12 +32,7 @@ export interface IFlowGraphGetPropertyBlockConfiguration<O extends FlowGraphAsse
*
* Note that it is recommended to input the object on which you are working on (i.e. a material) rather than providing a mesh as object and then getting the material from it.
*/
export class FlowGraphGetPropertyBlock<P extends any, O extends FlowGraphAssetType> extends FlowGraphBlock {
/**
* Output connection: The value of the property.
*/
public readonly value: FlowGraphDataConnection<P>;

export class FlowGraphGetPropertyBlock<P extends any, O extends FlowGraphAssetType> extends FlowGraphCachedOperationBlock<P> {
/**
* Input connection: The asset from which the property will be retrieved
*/
Expand All @@ -48,11 +43,6 @@ export class FlowGraphGetPropertyBlock<P extends any, O extends FlowGraphAssetTy
*/
public readonly propertyName: FlowGraphDataConnection<string>;

/**
* Output connection: Whether the value is valid.
*/
public readonly isValid: FlowGraphDataConnection<boolean>;

/**
* Input connection: A function that can be used to get the value of the property.
* This will be used if defined, instead of the default get function.
Expand All @@ -65,15 +55,13 @@ export class FlowGraphGetPropertyBlock<P extends any, O extends FlowGraphAssetTy
*/
public override config: IFlowGraphGetPropertyBlockConfiguration<O>
) {
super(config);
super(RichTypeAny, config);
this.object = this.registerDataInput("object", RichTypeAny, config.object);
this.propertyName = this.registerDataInput("propertyName", RichTypeAny, config.propertyName);
this.value = this.registerDataOutput("value", RichTypeAny);
this.isValid = this.registerDataOutput("isValid", RichTypeAny, false);
this.customGetFunction = this.registerDataInput("customGetFunction", RichTypeAny);
}

public override _updateOutputs(context: FlowGraphContext): void {
public override _doOperation(context: FlowGraphContext): P | undefined {
const getter = this.customGetFunction.getValue(context);
let value;
if (getter) {
Expand All @@ -83,13 +71,7 @@ export class FlowGraphGetPropertyBlock<P extends any, O extends FlowGraphAssetTy
const propertyName = this.propertyName.getValue(context);
value = target && propertyName ? this._getPropertyValue(target, propertyName) : undefined;
}
if (value === undefined) {
this.value.resetToDefaultValue(context);
this.isValid.setValue(false, context);
} else {
this.value.setValue(value, context);
this.isValid.setValue(true, context);
}
return value;
}

private _getPropertyValue(target: AssetType<O>, propertyName: string): P | undefined {
Expand Down
36 changes: 30 additions & 6 deletions packages/dev/loaders/src/glTF/2.0/Extensions/objectModelMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,32 @@ const nodesTree: IGLTFObjectModelTreeNodesObject = {
// readonly!
matrix: {
type: "Matrix",
get: (node: INode) => Matrix.Compose(node._babylonTransformNode?.scaling!, node._babylonTransformNode?.rotationQuaternion!, node._babylonTransformNode?.position!),
get: (node: INode) => node._babylonTransformNode?._localMatrix.clone(),
getTarget: (node: INode) => node._babylonTransformNode,
isReadOnly: true,
},
globalMatrix: {
type: "Matrix",
get: (node: INode) => node._babylonTransformNode?.getWorldMatrix(),
get: (node: INode) => {
const matrix = Matrix.Identity();
// RHS/LHS support
let rootNode = node.parent;
while (rootNode && rootNode.parent) {
rootNode = rootNode.parent;
}
if (rootNode) {
// take the parent root node's world matrix, invert it, and multiply it with the current node's world matrix
// This will provide the global matrix, ignoring the RHS->LHS conversion
const rootMatrix = rootNode._babylonTransformNode?.getWorldMatrix().invert();
if (rootMatrix) {
node._babylonTransformNode?.getWorldMatrix()?.multiplyToRef(rootMatrix, matrix);
}
} else if (node._babylonTransformNode) {
matrix.copyFrom(node._babylonTransformNode.getWorldMatrix());
}
return matrix;
},
getTarget: (node: INode) => node._babylonTransformNode,
getPropertyName: [() => "worldMatrixFromCache"],
isReadOnly: true,
},
extensions: {
Expand Down Expand Up @@ -953,14 +971,17 @@ function _GenerateTextureMap(textureType: keyof PBRMaterial, textureInObject?: s
const texture = _GetTexture(material, payload, textureType, textureInObject);
(texture.uOffset = value.x), (texture.vOffset = value.y);
},
getPropertyName: [() => `${textureType}.${textureInObject}.uOffset`, () => `${textureType}.${textureInObject}.vOffset`],
getPropertyName: [
() => `${textureType}${textureInObject ? "." + textureInObject : ""}.uOffset`,
() => `${textureType}${textureInObject ? "." + textureInObject : ""}.vOffset`,
],
},
rotation: {
type: "number",
get: (material, _index?, payload?) => _GetTexture(material, payload, textureType, textureInObject)?.wAng,
getTarget: _GetMaterial,
set: (value, material, _index?, payload?) => (_GetTexture(material, payload, textureType, textureInObject).wAng = value),
getPropertyName: [() => `${textureType}.${textureInObject}.wAng`],
getPropertyName: [() => `${textureType}${textureInObject ? "." + textureInObject : ""}.wAng`],
},
scale: {
componentsCount: 2,
Expand All @@ -974,7 +995,10 @@ function _GenerateTextureMap(textureType: keyof PBRMaterial, textureInObject?: s
const texture = _GetTexture(material, payload, textureType, textureInObject);
(texture.uScale = value.x), (texture.vScale = value.y);
},
getPropertyName: [() => `${textureType}.${textureInObject}.uScale`, () => `${textureType}.${textureInObject}.vScale`],
getPropertyName: [
() => `${textureType}${textureInObject ? "." + textureInObject : ""}.uScale`,
() => `${textureType}${textureInObject ? "." + textureInObject : ""}.vScale`,
],
},
};
}
Expand Down