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

Initial play animation branch commit. #2

Closed
Closed
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
11 changes: 11 additions & 0 deletions src/BasicBehaveEngine/BasicBehaveEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {WaitAll} from "./nodes/flow/WaitAll";
import {WhileLoop} from "./nodes/flow/WhileLoop";
import {WorldGet} from "./nodes/world/WorldGet";
import {WorldSet} from "./nodes/world/WorldSet";
import {PlayAnimation} from "./nodes/world/PlayAnimation";
import {WorldAnimateTo} from "./nodes/world/WorldAnimateTo";
import {Receive} from "./nodes/customEvent/Receive";
import {Send} from "./nodes/customEvent/Send";
Expand Down Expand Up @@ -125,6 +126,11 @@ export class BasicBehaveEngine implements IBehaveEngine {
this.jsonPtrTrie.addPath(jsonPtr, getterCallback, setterCallback, typeName);
}

public registerEngineCallback = (path: string, callback: (value: any, onComplete: ()=> void) => void) => {
console.log(`registering engine callback ${path}`);
this.jsonPtrTrie.addEngineCallback(path, callback);
}

public isValidJsonPtr = (jsonPtr: string): boolean => {
return this.jsonPtrTrie.isPathValid(jsonPtr);
}
Expand All @@ -137,6 +143,10 @@ export class BasicBehaveEngine implements IBehaveEngine {
return this.jsonPtrTrie.getPathTypeName(path);
}

public runEngineCallback(path: string, value: any, onComplete: ()=> void) {
this.jsonPtrTrie.runEngineCallback(path, value, onComplete);
}

public setPathValue = (path: string, value: any) => {
this.jsonPtrTrie.setPathValue(path, value);
}
Expand Down Expand Up @@ -242,6 +252,7 @@ export class BasicBehaveEngine implements IBehaveEngine {
this.registerBehaveEngineNode("flow/whileLoop", WhileLoop);
this.registerBehaveEngineNode("world/get", WorldGet);
this.registerBehaveEngineNode("world/set", WorldSet);
this.registerBehaveEngineNode("world/playAnimation", PlayAnimation);
this.registerBehaveEngineNode("world/animateTo", WorldAnimateTo);
this.registerBehaveEngineNode("math/abs", AbsoluteValue);
this.registerBehaveEngineNode("customEvent/receive", Receive);
Expand Down
8 changes: 8 additions & 0 deletions src/BasicBehaveEngine/IBehaveEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ export interface IBehaveEngine {
typeName: string
) => void;

/**
* Register a JSON pointer along with callback functions for getting and setting its value.
* @param path - The JSON pointer string.
* @param value - The value to use for the engine callback
* @param onComplete - the function to run whenever this is finished
*/
registerEngineCallback: (path: string, callback:(value: any, onComplete: ()=> void) => void) => void;

/**
* Register a Behave Engine node type along with its corresponding class.
* @param type - The type of the Behave Engine node.
Expand Down
12 changes: 12 additions & 0 deletions src/BasicBehaveEngine/JsonPtrTrie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class TrieNode {
trieNodeType: TrieNodeType;
setterCallback: ((path: string, value: any) => void) | undefined;
getterCallback: ((path: string) => any) | undefined;
engineCallbacks: Map<string, (value: any, onComplete: ()=> void) => void> = new Map<string, (value: any, onComplete: ()=> void) => void>();
typeName: string | undefined;


Expand All @@ -27,6 +28,11 @@ export class JsonPtrTrie {
this.root = new TrieNode(TrieNodeType.ROOT);
}

public addEngineCallback(path: string, callback: (value: any, onComplete: ()=> void) => void) {
console.log(`Adding engine callback for: ${path}`);
this.root.engineCallbacks.set(path, callback);
}

/**
* Adds a path to the JSON Pointer Trie along with getter and setter callbacks.
* @param path - The JSON Pointer path to add.
Expand Down Expand Up @@ -70,6 +76,12 @@ export class JsonPtrTrie {
currentNode.typeName = typeName;
}

public runEngineCallback(path: string, value: any, onComplete: ()=> void) {
const callback = this.root.engineCallbacks.get(path);
if (callback)
callback(value, onComplete);
}

/**
* Checks if a given JSON Pointer path is valid within the Trie.
* @param path - The JSON Pointer path to validate.
Expand Down
1 change: 1 addition & 0 deletions src/BasicBehaveEngine/decorators/ADecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export abstract class ADecorator implements IBehaveEngine {
abstract processAddingNodeToQueue: (flow: IFlow) => void;
abstract processExecutingNextNode: (flow: IFlow) => void;
abstract registerKnownPointers: () => void;
abstract registerEngineCallback: (path: string, callback: (value: any, onComplete: ()=> void) => void) => void;
abstract registerJsonPointer: (jsonPtr: string, getterCallback: (path: string) => any, setterCallback: (path: string, value: any) => void, typeName: string) => void;
abstract animateProperty: (type: string, path: string, easingType: string, easingDuration: number, initialValue: any, targetValue: any, callback: () => void) => void;

Expand Down
97 changes: 65 additions & 32 deletions src/BasicBehaveEngine/decorators/BabylonDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,39 +86,72 @@ export class BabylonDecorator extends ADecorator {
this.behaveEngine.registerJsonPointer(jsonPtr, getterCallback, setterCallback, typeName);
};

registerEngineCallback = (path: string, callback: (value: any, onComplete: ()=> void) => void) => {
this.behaveEngine.registerEngineCallback(path, callback);
}

registerKnownPointers = () => {
this.registerJsonPointer("nodes/99/scale", (path) => {
const parts: string[] = path.split("/");
return [(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).scaling.x,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).scaling.y,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).scaling.z];
}, (path, value) => {
const parts: string[] = path.split("/");
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).scaling = new Vector3(value[0], value[1], value[2]);
}, "float3")

this.registerJsonPointer("nodes/99/translation", (path) => {
const parts: string[] = path.split("/");
return [(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).position.x,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).position.y,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).position.z];
}, (path, value) => {
const parts: string[] = path.split("/");
console.log(value);
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).position= new Vector3(value[0], value[1], value[2]);
}, "float3")

this.registerJsonPointer("nodes/99/rotation", (path) => {
const parts: string[] = path.split("/");
return [
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).rotationQuaternion?.w,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).rotationQuaternion?.x,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).rotationQuaternion?.y,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).rotationQuaternion?.z];
}, (path, value) => {
const parts: string[] = path.split("/");
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).rotationQuaternion = new Quaternion(value[1], value[2], value[3], value[0]);
}, "float4")
const gltfNodesSize:number = this.world.glTFNodes.length;

this.registerEngineCallback(`playAnimation`, (value, onComplete) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a path, I do like the idea of abstracting the callbacks outside of babylon, but maybe we can do this someplace other than the json get/set trie

// Note : targetTime not yet implemented to do something yet
const {animation, speed, loopCount, targetTime} = value;
const animationGroup = this.scene.animationGroups[animation];

animationGroup.speedRatio = speed;

// run infinitely
if (loopCount <= 0) {
animationGroup.start(true, speed);
}
else {
let count = loopCount;
animationGroup.start(false, speed);
animationGroup.onAnimationGroupEndObservable.add(function() {
-- count;
if (count > 0) {
animationGroup.start(false, speed);
} else {
onComplete();
}
});
}
});

for (let i = 0; i < gltfNodesSize; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you only have to register it once for the highest number, so we could just do nodes/${glTFNodesSize}/scale etc..

this.registerJsonPointer(`nodes/${i}/scale`, (path) => {
const parts: string[] = path.split("/");
return [(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).scaling.x,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).scaling.y,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).scaling.z];
}, (path, value) => {
const parts: string[] = path.split("/");
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).scaling = new Vector3(value[0], value[1], value[2]);
}, "float3")

this.registerJsonPointer(`nodes/${i}/translation`, (path) => {
const parts: string[] = path.split("/");
return [(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).position.x,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).position.y,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).position.z];
}, (path, value) => {
const parts: string[] = path.split("/");
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).position= new Vector3(value[0], value[1], value[2]);
}, "float3")

this.registerJsonPointer(`nodes/${i}/rotation`, (path) => {
const parts: string[] = path.split("/");
return [
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).rotationQuaternion?.w,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).rotationQuaternion?.x,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).rotationQuaternion?.y,
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).rotationQuaternion?.z];
}, (path, value) => {
const parts: string[] = path.split("/");
(this.world.glTFNodes[Number(parts[1])] as AbstractMesh).rotationQuaternion = new Quaternion(value[1], value[2], value[3], value[0]);
}, "float4")

}
}

public extractBehaveGraphFromScene = (): any => {
Expand Down
4 changes: 4 additions & 0 deletions src/BasicBehaveEngine/decorators/LoggingDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export class LoggingDecorator extends ADecorator {
this.behaveEngine.registerJsonPointer(jsonPtr, getterCallback, setterCallback, typeName);
};

registerEngineCallback = (path: string, callback: (value: any, onComplete: ()=> void) => void) => {
this.behaveEngine.registerEngineCallback(path, callback);
}

animateProperty = (type: string, path: string, easingType: string, easingDuration: number, initialValue: any, targetValue: any, callback: () => void) => {
setTimeout(() => {
this.behaveEngine.setPathValue(path, targetValue);
Expand Down
48 changes: 48 additions & 0 deletions src/BasicBehaveEngine/nodes/world/PlayAnimation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {BehaveEngineNode, IBehaviourNodeProps} from "../../BehaveEngineNode";

export class PlayAnimation extends BehaveEngineNode {
REQUIRED_VALUES = [{id: "animation"}, {id: "speed"}, {id: "loopCount"}, {id: "targetTime"}]

_animation: number;
_speed: number;
_loopCount: number;
_targetTime: number;

constructor(props: IBehaviourNodeProps) {
super(props);
this.name = "PlayAnimation";
this.validateValues(this.values);
this.validateFlows(this.flows);
this.validateConfigurations(this.configuration);

const {animation, speed, loopCount, targetTime} = this.evaluateAllValues(this.REQUIRED_VALUES.map(val => val.id));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

values should not be evaluated at construction time, general practice is configuration = "class" private variables and the values are temporary variables only evaluated and used during the processNode function

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha


this._animation = animation;
this._speed = speed;
this._loopCount = loopCount;
this._targetTime = targetTime;
}

override processNode(flowSocket?: string) {
this.graphEngine.clearValueEvaluationCache();

const {animation, speed, loopCount, targetTime} = this.evaluateAllValues(this.REQUIRED_VALUES.map(val => val.id));

this._animation = animation;
this._speed = speed;
this._loopCount = loopCount;
this._targetTime = targetTime;

this.graphEngine.processNodeStarted(this);

console.log("trying to run play animation animation path value");
//const storeThisProcessNode = super.processNode;
const that = this;

this.graphEngine.runEngineCallback("playAnimation", {animation, speed, loopCount, targetTime}, function() {
if (that.flows.done) {
that.addEventToWorkQueue(that.flows.done)
}
});
}
}
52 changes: 52 additions & 0 deletions src/authoring/AuthoringNodeSpecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,58 @@ export const worldNodeSpecs: IAuthoringNode[] = [
values:[]
}
},
{
type: "world/playAnimation",
description: "Plays animation on the world",
configuration: [],
input: {
flows: [
{
id: "in",
description: "The in flow"
}
],
values: [
{
id: "animation",
types: [
"int",
],
description: "Target animation to run"
},
{
id: "speed",
types: [
"float",
],
description: "The speed to run the animation at"
},
{
id: "loopCount",
types: [
"int",
],
description: "Counts to loop animation"
},
{
id: "targetTime",
types: [
"float",
],
description: "Target time to run."
}
]
},
output: {
flows: [
{
id: "done",
description: "The out flow"
}
],
values:[]
}
},
{
type: "world/animateTo",
description: "Sets properties of the gltf using JSON pointer over a set time",
Expand Down
46 changes: 26 additions & 20 deletions src/components/engineViews/BabylonEngineComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,32 @@ export const BabylonEngineComponent = (props: {behaveGraphRef: any, setBehaveGra
const [fileUploaded, setFileUploaded] = useState(false);
const [clickedHotSpot, setClickedHotSpot] = useState<string | null>(null);

function createScene() {
// Create a scene
sceneRef.current = new Scene(engineRef.current!);

// Create a camera
const camera = new ArcRotateCamera('camera', Math.PI / 3, Math.PI / 3, 10, Vector3.Zero(), sceneRef.current);
camera.attachControl(canvasRef.current, true);
camera.minZ = 0.001;
canvasRef.current!.addEventListener("wheel", (e: any) => {
e.preventDefault();
e.stopPropagation();

return false;
})

// Create lights
new HemisphericLight('light1', new Vector3(0, 1, 0), sceneRef.current);
new DirectionalLight('light2', new Vector3(1, -1, 0), sceneRef.current);


}

useEffect(() => {
// Create the Babylon.js engines
engineRef.current = new Engine(canvasRef.current, true);

// Create a scene
sceneRef.current = new Scene(engineRef.current!);

// Create a camera
const camera = new ArcRotateCamera('camera', Math.PI / 3, Math.PI / 3, 10, Vector3.Zero(), sceneRef.current);
camera.attachControl(canvasRef.current, true);
camera.minZ = 0.001;
canvasRef.current!.addEventListener("wheel", (e: any) => {
e.preventDefault();
e.stopPropagation();

return false;
})

// Create lights
new HemisphericLight('light1', new Vector3(0, 1, 0), sceneRef.current);
new DirectionalLight('light2', new Vector3(1, -1, 0), sceneRef.current);

createScene();
// Run the render loop
engineRef.current?.runRenderLoop(() => {
sceneRef.current?.render();
Expand All @@ -76,7 +80,9 @@ export const BabylonEngineComponent = (props: {behaveGraphRef: any, setBehaveGra
}, []);

const resetScene = async () => {
sceneRef.current?.meshes.forEach(mesh => mesh.dispose())
sceneRef.current?.dispose();
createScene();

const file = fileInputRef.current!.files![0]

const url = URL.createObjectURL(file);
Expand Down