diff --git a/design/behavior.actions.md b/design/behavior.actions.md new file mode 100644 index 000000000..f21f40f5c --- /dev/null +++ b/design/behavior.actions.md @@ -0,0 +1,102 @@ +# Behavior Actions + +Behaviors are high level concepts that are made up of one or more actions. These actions make up the interactions +that the user makes with the given behavior. These actions are broken up in to three states: `started`, `performing` and `stopped`. + +- `started` - The state of the initial frame that an action has started.. +- `performing` - The state of all frames after `started` while the action is being performed. +- `stopped` - The state of the frame after the action has been stopped and all other frames after until started again. + +These actions result in events being raised from the runtime to the SDK for app developers code to listen to. These events are based +off of the threee action states above as such: + +- `started` - Fired once when the action has first transitioned to started. +- `performing` - Fired during synchronization updates while the action is being performed. This is an optional event that is exposed only on behaviors that provide them. +- `stopped` - Fired once when the action has first transitioned to the stopped state. + +**Proposed**: In addition, there is an optional update event that can be registered to the action for getting updates while that +action is being performed on the client. + +## Architecture + +### Action State +``` ts +export type ActionState + = 'started' + | 'performing' + | 'stopped' + ; +``` + +### Action Handler +``` ts +/** + * The action handler function type. + */ +export type ActionHandler = (user: User, actionData?: any) => void; +``` + +### Action API +``` ts +/** + * Add a handler for the given action state for when it is triggered. + * @param actionState The action state that the handle should be assigned to. + * @param handler The handler to call when the action state is triggered. + */ +public on(actionState: ActionState, handler: ActionHandler): this; + +/** + * Gets the current state of the action for the user with the given id. + * @param user The user to get the action state for. + * @returns The current state of the action for the user. + */ +public getState(user: User): ActionState; + +/** + * Get whether the action is active for the user with the given id. + * @param user - The user to get whether the action is active for, or null + * if active for any user is desired.. + * @returns - True if the action is active for the user, false if it is not. In the case + * that no user is given, the value is true if the action is active for any user, and false + * if not. + */ +public isActive(user?: User): boolean + +/** + * Add a handler for the performing update call for this action. Callback called on the + * standard actor update cadence while an action is being performed, accompanied by optional + * action data. + * @param handler The handler to call when the performing action update comes in from the client. + * **/ +public onPerformingUpdate((handler: ActionHandler): this; +``` + +## Network + +The `PerformAction` payload contains the type of state that the action is going through, as well as an +optional `actionData` object for the action being performed. There is also a third category for the +`PerformAction` payload that is `performingAction` which uses the payload to send up `actionData` if the +behavior wished to convey this additional data for while the action is being performed. + +### Perform Action Payload +``` ts +/** + * @hidden + * Engine to app. The user is performing an action for a behavior. + */ +export type PerformAction = Payload & { + type: 'perform-action'; + userId: Guid; + targetId: Guid; + behaviorType: BehaviorType; + actionName: string; + actionState: ActionState & `performing`; + actionData?: any; +}; +``` + +## Client + +The client will be respondible for sending `PerformAction` messages when an action is started or stopped, +as well as performing updates on the standard actor update cadence. Any date that the specific action should +pass along in the context of the behavior will be passed along in the optional action data object in the payload. \ No newline at end of file diff --git a/design/high.resolution.transform.updates.md b/design/high.resolution.transform.updates.md new file mode 100644 index 000000000..d35b65f42 --- /dev/null +++ b/design/high.resolution.transform.updates.md @@ -0,0 +1,42 @@ +# High Resolution Actor Transform Updates + +There are cases that a developer may want a higher resolution of transform updates that occur for an actor. +To facilitate this and allow for still having a regulated bandwidth, we can add an additional API on the actor +to allow for a developer to register/de-register for high resolution updates that would give an array of transforms +containing the full high resolution path that occured for the actor during the 10 hz regulated network update. + +## API + + A new actor API will be exposed to allow a developer to register and de-register for high resolution + transform updates. + +``` ts +/** + * Add a callback to the actor to receive path updates for while the tool is being used + */ +public onHighResTransfromUpdate((path: Transform[]) => void) { + // Path callback registration. + // Enable high resolution updates on the client. +} + +/** + * Remove a callback to the actor to receive path updates for while the tool is being used + */ +public offHighResTransfromUpdate((path: Transform[]) => void) { + // Path callback de-registration. + // Disable high resolution updates on the client. +} + +``` + +## Network + +The actor update structure and payload will now need to be able to accept an array of transforms per update. +This array would contain one transform in the case of normal resolution transform updates, and more in the +case that high resolution transform updates were enabled for the actor. + +## Client Implementation + +The client side changes will require a subscription mechanism on the actor runtime instance to signal that +high resolution updates need to be gathered for the actor during a fixed update interval and queuded to be +sent up with the normal 10 hz update message for the actor. \ No newline at end of file diff --git a/design/pen.behavior.md b/design/pen.behavior.md new file mode 100644 index 000000000..628522299 --- /dev/null +++ b/design/pen.behavior.md @@ -0,0 +1,87 @@ +# Pen Behavior + +The pen behavior is a method of recording the path by which a user moves a grabbed object that they activate so that various scenearios +can be carried out that wish to capture the virtual drawing of a user. This behavior will carry with it client side visuals +to ensure low-latency feedback, but will supply the complete curve data needed by the MRE app to acto on once the drawing is done. +The `PenBehavior` will be an extension of the `ToolBehavior`. + +## Architecture + +There will be a new `PenBehavior` that extends the `ToolBehavior` that will have the ability to generate a wet ink effect along the use +path on the client. + +### Behavior +``` ts +export interface PenBehaviorInitOptions { + drawOriginActorId: Guid; +} + +/** + * Pen behavior class containing the target behavior actions. + */ +export class PenBehavior extends ToolBehavior { + public drawOriginActorId: Guid; + + /** @inheritdoc */ + public get behaviorType(): BehaviorType { return 'pen'; } +} +``` + +### Draw Data + +``` ts +export interface DrawDataLike { + transform: Transform; + // Potentially additional data to come, such as: + pressure: number; +} +``` + +## Network + +The network messages for this behavior will use the standard behavior payloads for actions started and stopped. The two actions supported +by the Pen behavior are hold (begin/end) and drawing (begin/end). + +In addition to the standard `PerformAction` start and stop payload, there will be a third `PerformAction` type for `perfoming` which will +contain the draw data in the optional action data of the `PerformAction` payload. + +## Sync layer considerations + +Behaviors already have sync layer filtering to ensure that the action messages are for the client they happen on to the MRE app only. Grab +also has special sync filtering based on the client that the grab is happening and the rest of the clients. + +## Client implementation + +The client will require the addition of a new pen behavior for the host app to implement, as well as a draw recording system that will facilitate +the recording of the transform data over time while a draw is happening and to send that data in chunks during the draw based on a set frequency. + +## Example Usage + +``` ts +// Create an actor +this.penModel = MRE.Actor.CreateFromGltf(this.assets, { + // from the glTF at the given URL, with box colliders on each mesh + uri: `${this.baseUrl}/pen.glb`, + colliderType: 'mesh', + // Also apply the following generic actor properties. + actor: { + name: 'penTool', + parentId: root.id, + transform: { + local: { + scale: { x: 0.4, y: 0.4, z: 0.4 }, + position: { x: 0, y: 1, z: -1 } + } + } + } +}); + +const penBehavior = Actor.SetBehavior(); + +penBehavior.onDrawEventReceived((drawData) => { + // Hypothetical spline generator tool in node. + const path = drawData.map(dd => dd.transform); + const spline = SplineGenerator.generateSpline(path); + // Generate a mesh from the spline and use it to create a new actor at the point of origin of the draw. +}); +``` \ No newline at end of file diff --git a/design/set.behavior.actor.md b/design/set.behavior.actor.md new file mode 100644 index 000000000..f44e38a39 --- /dev/null +++ b/design/set.behavior.actor.md @@ -0,0 +1,33 @@ +# Set Behavior API + +An API is needed on the actor for the developer to be able to set a bahavior for the actor to use to +allow fo user interaction with that actor. The API should ensure that the behavior is tied to the one +actor it is being set on and should be flexible enough to allow setting params on the the behavior at the +time of creation or initialization. + +## API + +### In actor.ts +``` ts +/** + * Sets the behavior on this actor. + * @param behavior The type of behavior to set. Pass null to clear the behavior. + * @param initOptions The init options object to pass to the behavior to initialize it. + */ +public setBehavior( + behavior: { new(initOptions?: InitOptionsT): BehaviorT }, + initOptions?: InitOptionsT +): BehaviorT { + if (behavior) { + const newBehavior = new behavior(initOptions); + + this.internal.behavior = newBehavior; + this.context.internal.setBehavior(this.id, this.internal.behavior.behaviorType); + return newBehavior; + } + + this.internal.behavior = null; + this.context.internal.setBehavior(this.id, null); + return null; +} +``` \ No newline at end of file diff --git a/design/tool.behavior.md b/design/tool.behavior.md new file mode 100644 index 000000000..39657e2c6 --- /dev/null +++ b/design/tool.behavior.md @@ -0,0 +1,81 @@ +# Tool Behavior + +The tool behavior allows the user to assign this behavior to an actor so that the actor can be held and +used. + +## Architecture + +Create a new behavior that is an `ToolBehavior` for being able to add to an actor, that when added will +automatically enable grabbable on that actor. This behavior will then allow the user to execute a primary +action to begin recording the transform changes over time. + +``` ts +/** + * Tool behavior class containing the target behavior actions. + */ +export class ToolBehavior extends TargetBehavior { + private _holding: DiscreteAction = new DiscreteAction(); + private _using: DiscreteAction = new DiscreteAction(); + + /** @inheritdoc */ + public get behaviorType(): BehaviorType { return 'tool'; } + + public get holding() { return this._holding; } + public get using() { return this._using; } + + /** + * Add a holding handler to be called when the given hover state is triggered. + * @param holdingState The holding state to fire the handler on. + * @param handler The handler to call when the holding state is triggered. + * @return This tool behavior. + */ + public onHolding(holdingState: 'grabbed' | 'dropped', handler: ActionHandler): this { + const actionState: ActionState = (holdingState === 'grabbed') ? 'started' : 'stopped'; + this._holding.on(actionState, handler); + return this; + } + + /** + * Add a using handler to be called when the given hover state is triggered. + * @param usingState The using state to fire the handler on. + * @param handler The handler to call when the using state is triggered. + * @return This tool behavior. + */ + public onUsing(usingState: 'started' | 'stopped', handler: ActionHandler): this { + const actionState: ActionState = (hoverState === 'started') ? 'started' : 'stopped'; + this._using.on(actionState, handler); + return this; + } +} +``` + +## Network + +The network messages will use the behavior payloads of SetBehavior and PerformAction payloads the same way all +behaviors do. In addition, there is an additional message that would come in the form of the path of the tool +while it is being used. This will be sent with a higher fidelity than the normal 10 hz transform updates, and +will be in the form of a collection of transform recordings. + +The message payload for the using path updates could look like the following: + +``` ts +export type UsingToolPath = Payload & { + type: 'using-tool-path'; + actorId: toolActorId; + path: Transform[]; +} +``` + +## Sync layer considerations + +Synchronization will follow the same procedure as all other behaviors. Nothing special about this behavior. +The additional sync concerns will center around the path data being sent to the MRE app from the tool behavior +when it is being used. This path data only needs to be sent to the MRE app and not the other clients, as the +MRE app will be responsible for doing what it wants with the path, or behavior that extends `ToolBehavior` may +have its own work it does with this path. + +## Client implementation + +Client implementation is similar to all other behaviors in that a handler will be created for this behavior, as +well as an interface for the client app to implement the actual behavior with. Additionally, a path recording +system will need to be developed to generate the path recording while the tool is in use. \ No newline at end of file diff --git a/packages/functional-tests/src/tests/physics-bounce-test.ts b/packages/functional-tests/src/tests/physics-bounce-test.ts index 6ef5fc477..5c4527a10 100644 --- a/packages/functional-tests/src/tests/physics-bounce-test.ts +++ b/packages/functional-tests/src/tests/physics-bounce-test.ts @@ -17,11 +17,16 @@ export default class PhysicsBounceTest extends Test { public async run(root: MRE.Actor): Promise { this.assets = new MRE.AssetContainer(this.app.context); - this.materials.push(this.assets.createMaterial('mat1', { color: MRE.Color3.FromHexString('#ff0000').toColor4() })); - this.materials.push(this.assets.createMaterial('mat2', { color: MRE.Color3.FromHexString('#ff7700').toColor4() })); - this.materials.push(this.assets.createMaterial('mat3', { color: MRE.Color3.FromHexString('#ffbd00').toColor4() })); - this.materials.push(this.assets.createMaterial('mat4', { color: MRE.Color3.FromHexString('#fcff00').toColor4() })); - this.materials.push(this.assets.createMaterial('mat5', { color: MRE.Color3.FromHexString('#abf300').toColor4() })); + this.materials.push(this.assets.createMaterial('mat1', + { color: MRE.Color3.FromHexString('#ff0000').toColor4() })); + this.materials.push(this.assets.createMaterial('mat2', + { color: MRE.Color3.FromHexString('#ff7700').toColor4() })); + this.materials.push(this.assets.createMaterial('mat3', + { color: MRE.Color3.FromHexString('#ffbd00').toColor4() })); + this.materials.push(this.assets.createMaterial('mat4', + { color: MRE.Color3.FromHexString('#fcff00').toColor4() })); + this.materials.push(this.assets.createMaterial('mat5', + { color: MRE.Color3.FromHexString('#abf300').toColor4() })); this.createBouncePlane(root, 2, 1.25); diff --git a/packages/functional-tests/src/tests/physics-friction-test.ts b/packages/functional-tests/src/tests/physics-friction-test.ts index 9db182795..45ea76366 100644 --- a/packages/functional-tests/src/tests/physics-friction-test.ts +++ b/packages/functional-tests/src/tests/physics-friction-test.ts @@ -17,11 +17,16 @@ export default class PhysicsFrictionTest extends Test { public async run(root: MRE.Actor): Promise { this.assets = new MRE.AssetContainer(this.app.context); - this.materials.push(this.assets.createMaterial('mat1', { color: MRE.Color3.FromHexString('#2b7881').toColor4() })); - this.materials.push(this.assets.createMaterial('mat2', { color: MRE.Color3.FromHexString('#11948b').toColor4() })); - this.materials.push(this.assets.createMaterial('mat3', { color: MRE.Color3.FromHexString('#664a72').toColor4() })); - this.materials.push(this.assets.createMaterial('mat4', { color: MRE.Color3.FromHexString('#89133d').toColor4() })); - this.materials.push(this.assets.createMaterial('mat5', { color: MRE.Color3.FromHexString('#c7518e').toColor4() })); + this.materials.push(this.assets.createMaterial('mat1', + { color: MRE.Color3.FromHexString('#2b7881').toColor4() })); + this.materials.push(this.assets.createMaterial('mat2', + { color: MRE.Color3.FromHexString('#11948b').toColor4() })); + this.materials.push(this.assets.createMaterial('mat3', + { color: MRE.Color3.FromHexString('#664a72').toColor4() })); + this.materials.push(this.assets.createMaterial('mat4', + { color: MRE.Color3.FromHexString('#89133d').toColor4() })); + this.materials.push(this.assets.createMaterial('mat5', + { color: MRE.Color3.FromHexString('#c7518e').toColor4() })); this.createSlopePlane(root, 2, 1.25); diff --git a/packages/sdk/src/actor/actorInternal.ts b/packages/sdk/src/actor/actorInternal.ts index b6a0b3cb4..568d42696 100644 --- a/packages/sdk/src/actor/actorInternal.ts +++ b/packages/sdk/src/actor/actorInternal.ts @@ -4,13 +4,13 @@ */ import { + Actionable, ActionEvent, Actor, ActorLike, Behavior, CollisionData, CollisionEventType, - DiscreteAction, TriggerEventType } from '..'; import { @@ -40,11 +40,15 @@ export class ActorInternal implements InternalPatchable { const behavior = (this.behavior && this.behavior.behaviorType === actionEvent.behaviorType) ? this.behavior : undefined; if (behavior && behavior._supportsAction(actionEvent.actionName)) { - behavior._performAction(actionEvent.actionName, actionEvent.actionState, actionEvent.user); + behavior._performAction( + actionEvent.actionName, + actionEvent.actionState, + actionEvent.user, + actionEvent.actionData); } else { - const action = (this.actor as any)[actionEvent.actionName.toLowerCase()] as DiscreteAction; + const action = (this.actor as any)[`_${actionEvent.actionName.toLowerCase()}`] as Actionable; if (action) { - action._setState(actionEvent.user, actionEvent.actionState); + action._performAction(actionEvent.user, actionEvent.actionState, actionEvent.actionData); } } } diff --git a/packages/sdk/src/actor/behaviors/action.ts b/packages/sdk/src/actor/behaviors/action.ts index f72d01f22..86f13fc3f 100644 --- a/packages/sdk/src/actor/behaviors/action.ts +++ b/packages/sdk/src/actor/behaviors/action.ts @@ -1,86 +1,94 @@ -/*! - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -import { ActionState, Guid, User } from '../..'; - -/** - * The action handler function type. - */ -export type ActionHandler = (user: User) => void; - -interface ActionHandlers { - 'started'?: ActionHandler; - 'stopped'?: ActionHandler; -} - -/** - * Class that represents a discrete action that can be in one of two states, - * started or stopped for each user. @see ActionState - */ -export class DiscreteAction { - private handlers: ActionHandlers = {}; - private activeUserIds: Guid[] = []; - - /** - * Add a handler for the given action state for when it is triggered. - * @param actionState The action state that the handle should be assigned to. - * @param handler The handler to call when the action state is triggered. - */ - public on(actionState: ActionState, handler: ActionHandler): this { - this.handlers[actionState] = handler; - return this; - } - - /** - * Gets the current state of the action for the user with the given id. - * @param user The user to get the action state for. - * @returns The current state of the action for the user. - */ - public getState(user: User): ActionState { - return this.activeUserIds.find(id => id === user.id) ? - 'started' : 'stopped'; - } - - /** - * Get whether the action is active for the user with the given id. - * @param user - The user to get whether the action is active for, or null - * if active for any user is desired.. - * @returns - True if the action is active for the user, false if it is not. In the case - * that no user is given, the value is true if the action is active for any user, and false - * if not. - */ - public isActive(user?: User): boolean { - if (user) { - return !!this.activeUserIds.find(id => id === user.id); - } else { - return this.activeUserIds.length > 0; - } - } - - /** - * INTERNAL METHODS - */ - - /** @hidden */ - public _setState(user: User, actionState: ActionState): boolean { - const currentState = this.activeUserIds.find(id => id === user.id) || 'stopped'; - if (currentState !== actionState) { - if (actionState === 'started') { - this.activeUserIds.push(user.id); - } else { - this.activeUserIds = this.activeUserIds.filter(id => id === user.id); - } - - const handler = this.handlers[actionState]; - if (handler) { - handler(user); - } - - return true; - } - - return false; - } -} +/*! + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { ActionState, Guid, User } from '../..'; + +/** + * The action handler function type. + */ +export type ActionHandler = (user: User, actionData?: ActionDataT) => void; + +/** + * @hidden + */ +export interface Actionable { + _performAction(user: User, actionState: ActionState, actionData?: any): boolean; +} + +interface ActionHandlers { + 'started'?: ActionHandler; + 'stopped'?: ActionHandler; + 'performing'?: ActionHandler; +} + +/** + * Class that represents a discrete action that can be in one of two states, + * started or stopped for each user. @see ActionState + */ +export class DiscreteAction implements Actionable { + private handlers: ActionHandlers = {}; + private activeUserIds: Guid[] = []; + + /** + * Add a handler for the given action state for when it is triggered. + * @param actionState The action state that the handle should be assigned to. + * @param handler The handler to call when the action state is triggered. + */ + public on(actionState: ActionState, handler: ActionHandler): this { + this.handlers[actionState] = handler; + return this; + } + + /** + * Gets the current state of the action for the user with the given id. + * @param user The user to get the action state for. + * @returns The current state of the action for the user. + */ + public getState(user: User): ActionState { + return this.activeUserIds.find(id => id === user.id) ? + 'performing' : 'stopped'; + } + + /** + * Get whether the action is active for the user with the given id. + * @param user - The user to get whether the action is active for, or null + * if active for any user is desired.. + * @returns - True if the action is active for the user, false if it is not. In the case + * that no user is given, the value is true if the action is active for any user, and false + * if not. + */ + public isActive(user?: User): boolean { + if (user) { + return !!this.activeUserIds.find(id => id === user.id); + } else { + return this.activeUserIds.length > 0; + } + } + + /** + * INTERNAL METHODS + */ + + /** @hidden */ + public _performAction(user: User, actionState: ActionState, actionData?: any): boolean { + const currentState = this.activeUserIds.find(id => id === user.id) || 'stopped'; + if (currentState !== actionState) { + if (actionState === 'started') { + this.activeUserIds.push(user.id); + } else if (actionState === 'stopped') { + this.activeUserIds = this.activeUserIds.filter(id => id === user.id); + } + + const handler = this.handlers[actionState]; + if (handler) { + handler(user, actionData); + } + + return true; + } + + return false; + } +} diff --git a/packages/sdk/src/actor/behaviors/actionEvent.ts b/packages/sdk/src/actor/behaviors/actionEvent.ts index 697c81bce..cfca3157a 100644 --- a/packages/sdk/src/actor/behaviors/actionEvent.ts +++ b/packages/sdk/src/actor/behaviors/actionEvent.ts @@ -11,4 +11,5 @@ export interface ActionEvent { behaviorType: BehaviorType; actionName: string; actionState: ActionState; + actionData?: any; } diff --git a/packages/sdk/src/actor/behaviors/actionStates.ts b/packages/sdk/src/actor/behaviors/actionStates.ts index 698e25ddf..45caa6f6f 100644 --- a/packages/sdk/src/actor/behaviors/actionStates.ts +++ b/packages/sdk/src/actor/behaviors/actionStates.ts @@ -5,5 +5,6 @@ export type ActionState = 'started' + | 'performing' | 'stopped' ; diff --git a/packages/sdk/src/actor/behaviors/behavior.ts b/packages/sdk/src/actor/behaviors/behavior.ts index 8eb3050de..6c53ea85c 100644 --- a/packages/sdk/src/actor/behaviors/behavior.ts +++ b/packages/sdk/src/actor/behaviors/behavior.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. */ -import { ActionState, BehaviorType, DiscreteAction, User } from '../..'; +import { ActionState, BehaviorType, Actionable } from '.'; +import { User } from '../../user'; /** * Abstract class that serves as the base class for all behaviors. @@ -19,15 +20,15 @@ export abstract class Behavior { */ public _supportsAction(actionName: string): boolean { - const action = (this as any)[actionName.toLowerCase()] as DiscreteAction; + const action = (this as any)[`_${actionName.toLowerCase()}`] as Actionable; return action !== undefined; } /** @hidden */ - public _performAction(actionName: string, actionState: ActionState, user: User): void { - const action = (this as any)[actionName.toLowerCase()] as DiscreteAction; + public _performAction(actionName: string, actionState: ActionState, user: User, actionData: any): void { + const action = (this as any)[`_${actionName.toLowerCase()}`] as Actionable; if (action) { - action._setState(user, actionState); + action._performAction(user, actionState, actionData); } } } diff --git a/packages/sdk/src/actor/behaviors/buttonBehavior.ts b/packages/sdk/src/actor/behaviors/buttonBehavior.ts index 538a9ef50..bdb96f014 100644 --- a/packages/sdk/src/actor/behaviors/buttonBehavior.ts +++ b/packages/sdk/src/actor/behaviors/buttonBehavior.ts @@ -24,10 +24,6 @@ export class ButtonBehavior extends TargetBehavior { /** @inheritdoc */ public get behaviorType(): BehaviorType { return 'button'; } - public get hover() { return this._hover; } - public get click() { return this._click; } - public get button() { return this._button; } - /** * Add a hover handler to be called when the given hover state is triggered. * @param hoverState The hover state to fire the handler on. diff --git a/packages/sdk/src/actor/behaviors/index.ts b/packages/sdk/src/actor/behaviors/index.ts index ce677d1b4..4175916b5 100644 --- a/packages/sdk/src/actor/behaviors/index.ts +++ b/packages/sdk/src/actor/behaviors/index.ts @@ -10,3 +10,5 @@ export * from './behavior'; export * from './behaviorTypes'; export * from './buttonBehavior'; export * from './targetBehavior'; +export * from './toolBehavior'; +export * from './penBehavior'; \ No newline at end of file diff --git a/packages/sdk/src/actor/behaviors/penBehavior.ts b/packages/sdk/src/actor/behaviors/penBehavior.ts new file mode 100644 index 000000000..1d2fce10a --- /dev/null +++ b/packages/sdk/src/actor/behaviors/penBehavior.ts @@ -0,0 +1,28 @@ +/*! + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { ToolBehavior } from '.'; +import { TransformLike } from '../..'; + + +interface DrawData { + transform: TransformLike; + // Potentially additional data to come, such as: + // pressure: number; +} + +interface PenEventData { + drawData: DrawData[]; +} + +/** + * Pen behavior class containing the target behavior actions. + */ +export abstract class PenBehavior extends ToolBehavior { + // private drawOriginActorId: Guid; + + /** @inheritdoc */ + // public get behaviorType(): BehaviorType { return 'pen'; } +} diff --git a/packages/sdk/src/actor/behaviors/targetBehavior.ts b/packages/sdk/src/actor/behaviors/targetBehavior.ts index bceb4bdaa..f86da6933 100644 --- a/packages/sdk/src/actor/behaviors/targetBehavior.ts +++ b/packages/sdk/src/actor/behaviors/targetBehavior.ts @@ -22,8 +22,6 @@ export class TargetBehavior extends Behavior { /** @inheritdoc */ public get behaviorType(): BehaviorType { return 'target'; } - public get target() { return this._target; } - /** * Add a target handler to be called when the given target state is triggered. * @param targetState The target state to fire the handler on. diff --git a/packages/sdk/src/actor/behaviors/toolBehavior.ts b/packages/sdk/src/actor/behaviors/toolBehavior.ts new file mode 100644 index 000000000..310874e54 --- /dev/null +++ b/packages/sdk/src/actor/behaviors/toolBehavior.ts @@ -0,0 +1,37 @@ +/*! + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { ActionHandler, ActionState, DiscreteAction, TargetBehavior } from '.'; + +export abstract class ToolBehavior extends TargetBehavior { + private _holding: DiscreteAction = new DiscreteAction(); + private _using: DiscreteAction = new DiscreteAction(); + + /** + * Add a holding handler to be called when the given hover state is triggered. + * @param holdingState The holding state to fire the handler on. + * @param handler The handler to call when the holding state is triggered. + * @return This tool behavior. + */ + public onHolding(holdingState: 'picked-up' | 'holding' | 'dropped', handler: ActionHandler): this { + const actionState: ActionState = + (holdingState === 'picked-up') ? 'started' : (holdingState === 'holding') ? 'performing' : 'stopped'; + this._holding.on(actionState, handler); + return this; + } + + /** + * Add a using handler to be called when the given hover state is triggered. + * @param usingState The using state to fire the handler on. + * @param handler The handler to call when the using state is triggered. + * @return This tool behavior. + */ + public onUsing(usingState: 'started' | 'using' | 'stopped', handler: ActionHandler): this { + const actionState: ActionState = + (usingState === 'started') ? 'started' : (usingState === 'using') ? 'performing' : 'stopped'; + this._using.on(actionState, handler); + return this; + } +} diff --git a/packages/sdk/src/internal/payloads/payloads.ts b/packages/sdk/src/internal/payloads/payloads.ts index 1e0ae8fe8..1ceb3082a 100644 --- a/packages/sdk/src/internal/payloads/payloads.ts +++ b/packages/sdk/src/internal/payloads/payloads.ts @@ -307,6 +307,7 @@ export type PerformAction = Payload & { behaviorType: BehaviorType; actionName: string; actionState: ActionState; + actionData?: any; }; /** diff --git a/packages/sdk/src/internal/protocols/execution.ts b/packages/sdk/src/internal/protocols/execution.ts index fc34e7635..2df7ecdf6 100644 --- a/packages/sdk/src/internal/protocols/execution.ts +++ b/packages/sdk/src/internal/protocols/execution.ts @@ -111,7 +111,8 @@ export class Execution extends Protocol { targetId: payload.targetId, behaviorType: payload.behaviorType, actionName: payload.actionName, - actionState: payload.actionState + actionState: payload.actionState, + actionData: payload.actionData, } as ActionEvent); };