diff --git a/packages/aardvark-react/src/aardvark_gadget_seed.tsx b/packages/aardvark-react/src/aardvark_gadget_seed.tsx index 92e1370c..385f81f5 100644 --- a/packages/aardvark-react/src/aardvark_gadget_seed.tsx +++ b/packages/aardvark-react/src/aardvark_gadget_seed.tsx @@ -36,7 +36,6 @@ enum GadgetSeedPhase Idle, GrabberNearby, WaitingForGadgetStart, - WaitingForGadgetInContainer, WaitingForRegrab, WaitingForRedropToFinish, } @@ -58,10 +57,12 @@ export class GadgetSeedContainerComponent implements EntityComponent private entityCallback: () => void = null; private activeContainer: ActiveInterface = null; private childAddedCallback: () => void; + private childRemovedCallback: () => void; - constructor(childAddedCallback: () => void) + constructor( childAddedCallback: () => void, childRemovedCallback: () => void ) { this.childAddedCallback = childAddedCallback; + this.childRemovedCallback = childRemovedCallback; } private updateListener() @@ -89,6 +90,7 @@ export class GadgetSeedContainerComponent implements EntityComponent this.contentsEpa = null; this.activeContainer = null; this.updateListener(); + this.childRemovedCallback?.(); } ); this.activeContainer = activeContainer; @@ -165,7 +167,7 @@ export class GadgetSeedContainerComponent implements EntityComponent export class AvGadgetSeed extends React.Component< AvGadgetSeedProps, AvGadgetSeedState > { private moveableComponent = new MoveableComponent( this.onMoveableUpdate ); - private containerComponent = new GadgetSeedContainerComponent( this.triggerRegrab ); + private containerComponent = new GadgetSeedContainerComponent( this.onNewChild, this.onLostChild ); private refContainer = React.createRef(); private refSeed = React.createRef(); @@ -221,28 +223,42 @@ export class AvGadgetSeed extends React.Component< AvGadgetSeedProps, AvGadgetSe // Our next internal state change will be driven by the gadget starting break; - case GadgetSeedPhase.WaitingForRegrab: - switch( this.moveableComponent.state ) - { - case MoveableComponentState.InContainer: - case MoveableComponentState.Idle: - case MoveableComponentState.GrabberNearby: - // we've been dropped - this.setState( { phase: GadgetSeedPhase.Idle } ); - this.moveableComponent.reset(); - break; - - case MoveableComponentState.Grabbed: - // still waiting; - break; - } + } + } + + @bind + private onNewChild() + { + console.log( "onNewChild" ); + switch ( this.moveableComponent.state ) + { + case MoveableComponentState.Grabbed: + console.log( `regrabbing new gadget moveable ${ endpointAddrToString( this.containerComponent.child ) }` ); + this.triggerRegrab(); + break; + case MoveableComponentState.GrabberNearby: + case MoveableComponentState.InContainer: + console.log( `redropping new gadget moveable ${ endpointAddrToString( this.containerComponent.child ) }` ); + this.containerComponent.redrop( this.moveableComponent.parent, this.refSeed.current.globalId ); + this.setState( { phase: GadgetSeedPhase.WaitingForRedropToFinish } ); break; + case MoveableComponentState.Idle: + console.log( "How did we get all the way back to idle?" ); + break; + } + } + @bind + private onLostChild() + { + console.log( "lost child in container" ); + switch( this.state.phase ) + { case GadgetSeedPhase.WaitingForRedropToFinish: - if( this.moveableComponent.state == MoveableComponentState.Idle ) - { - this.setState( { phase: GadgetSeedPhase.Idle } ); - } + case GadgetSeedPhase.WaitingForRegrab: + // we've been dropped + this.setState( { phase: GadgetSeedPhase.Idle } ); + this.moveableComponent.reset(); break; } } @@ -259,21 +275,11 @@ export class AvGadgetSeed extends React.Component< AvGadgetSeedProps, AvGadgetSe if( !res.success ) { + this.setState( { phase: GadgetSeedPhase.Idle } ); throw new Error( "startGadget failed" ); } - - // we should have a gadget in our container by now? Maybe? - if( this.containerComponent.child ) - { - this.triggerRegrab(); - } - else - { - this.setState( { phase: GadgetSeedPhase.WaitingForGadgetInContainer } ); - } } - @bind private triggerRegrab() { this.moveableComponent.triggerRegrab( this.containerComponent.child, k_seedFromGadget ); diff --git a/packages/aardvark-react/src/component_moveable.ts b/packages/aardvark-react/src/component_moveable.ts index f51c9395..ca34e7bf 100644 --- a/packages/aardvark-react/src/component_moveable.ts +++ b/packages/aardvark-react/src/component_moveable.ts @@ -31,6 +31,7 @@ export enum GrabRequestType DropYourself = "drop_yourself", DropComplete = "drop_complete", SetGrabber = "set_grabber", + ReleaseMe = "release_me", RequestRegrab = "request_regrab", } @@ -49,9 +50,9 @@ export class MoveableComponent implements EntityComponent private entityCallback: () => void = null; private ownerCallback: () => void = null; - private activeGrab: ActiveInterface = null; + private activeGrabs = new Set(); private activeContainer: ActiveInterface = null; - private grabber: EndpointAddr = null; + private grabber: ActiveInterface = null; private wasEverDropped: boolean = false; private droppedIntoInitialParent: boolean = false; private initialInterface:InitialInterfaceLock = null; @@ -82,7 +83,7 @@ export class MoveableComponent implements EntityComponent { return MoveableComponentState.Grabbed; } - else if( this.activeGrab ) + else if( this.activeGrabs.size > 0 ) { return MoveableComponentState.GrabberNearby; } @@ -101,7 +102,7 @@ export class MoveableComponent implements EntityComponent { activeGrab.onEnded(() => { - this.activeGrab = null; + this.activeGrabs.delete( activeGrab ); this.updateListener(); } ); @@ -111,22 +112,32 @@ export class MoveableComponent implements EntityComponent switch( event.type ) { case GrabRequestType.SetGrabber: - this.grabber = this.activeGrab.peer; - - this.activeContainer?.sendEvent( { state: "Moving" } ); - this.activeContainer?.unlock(); - - this.updateListener(); + if( this.grabber == activeGrab ) + { + console.log( `SetGrabber from ${endpointAddrToString( activeGrab.peer ) }, which was already our grabber` ); + } + else + { + let release: GrabRequest = { type: GrabRequestType.ReleaseMe }; + this.grabber?.sendEvent( release ); + + this.grabber = activeGrab; + + this.activeContainer?.sendEvent( { state: "Moving" } ); + this.activeContainer?.unlock(); + + this.updateListener(); + } break; case GrabRequestType.DropYourself: await this.dropIntoContainer( true ); - this.activeGrab.sendEvent( { type: GrabRequestType.DropComplete } as GrabRequest ); + activeGrab.sendEvent( { type: GrabRequestType.DropComplete } as GrabRequest ); break; } } ); - this.activeGrab = activeGrab; + this.activeGrabs.add( activeGrab ); this.updateListener(); } @@ -152,7 +163,7 @@ export class MoveableComponent implements EntityComponent newMoveable: replacementMoveable, oldMoveableFromNewMoveable, } - this.activeGrab?.sendEvent( e ); + this.grabber?.sendEvent( e ); } @bind @@ -221,7 +232,7 @@ export class MoveableComponent implements EntityComponent if( this.grabber ) { // if we're currently grabbed, that's our parent - return this.grabber; + return this.grabber.peer; } else if( this.wasEverDropped ) { @@ -257,6 +268,7 @@ export class MoveableComponent implements EntityComponent public reset() { this.activeContainer?.unlock(); + this.activeContainer?.sendEvent( { state: "Moving" } ); this.wasEverDropped = false; this.grabber = null; this.updateListener(); diff --git a/packages/aardvark-react/src/math_utils.ts b/packages/aardvark-react/src/math_utils.ts index a039751f..d6394d02 100644 --- a/packages/aardvark-react/src/math_utils.ts +++ b/packages/aardvark-react/src/math_utils.ts @@ -82,6 +82,11 @@ export function nodeTransformFromMat4( m: mat4 ) : AvNodeTransform export function nodeTransformToMat4( transform: AvNodeTransform ): mat4 { + if( !transform ) + { + return mat4.identity; + } + let vTrans: vec3; if ( transform.position ) { diff --git a/websrc/default_hands/manifest.webmanifest b/websrc/default_hands/manifest.webmanifest index 665e8d2c..bc9d6204 100644 --- a/websrc/default_hands/manifest.webmanifest +++ b/websrc/default_hands/manifest.webmanifest @@ -10,8 +10,8 @@ ], "aardvark": { - "browserWidth": 16, - "browserHeight": 16, + "browserWidth": 512, + "browserHeight": 512, "permissions" : [ "scenegraph" ] } } \ No newline at end of file diff --git a/websrc/default_hands/src/default_hands.css b/websrc/default_hands/src/default_hands.css index ae977273..da0f568a 100644 --- a/websrc/default_hands/src/default_hands.css +++ b/websrc/default_hands/src/default_hands.css @@ -1,11 +1,16 @@ body, html { - background-color: transparent; + background-color: #FFFFFFAA; width: 100%; height: 100%; overflow: hidden; } +div +{ + font-size: large; +} + .FullPage { width: 100%; diff --git a/websrc/default_hands/src/default_hands_main.tsx b/websrc/default_hands/src/default_hands_main.tsx index ea09eb59..9ec8ee4d 100644 --- a/websrc/default_hands/src/default_hands_main.tsx +++ b/websrc/default_hands/src/default_hands_main.tsx @@ -1,4 +1,4 @@ -import { ActiveInterface, AvComposedEntity, AvEntityChild, AvGadget, AvInterfaceEntity, AvOrigin, AvPrimitive, AvTransform, GrabRequest, GrabRequestType, PanelRequest, PanelRequestType, PrimitiveType, SimpleContainerComponent, multiplyTransforms } from '@aardvarkxr/aardvark-react'; +import { ActiveInterface, AvComposedEntity, AvEntityChild, AvGadget, AvInterfaceEntity, AvOrigin, AvPrimitive, AvTransform, GrabRequest, GrabRequestType, PanelRequest, PanelRequestType, PrimitiveType, SimpleContainerComponent, multiplyTransforms, AvPanel } from '@aardvarkxr/aardvark-react'; import { AvNodeTransform, AvVolume, EAction, EHand, EVolumeType, g_builtinModelHandLeft, g_builtinModelHandRight, InterfaceLockResult, EndpointAddr, endpointAddrsMatch, endpointAddrToString } from '@aardvarkxr/aardvark-shared'; import bind from 'bind-decorator'; import * as React from 'react'; @@ -15,17 +15,18 @@ enum GrabberState Highlight, Grabbing, LostGrab, + GrabReleased, WaitingForDropComplete, WaitingForRegrab, WaitingForRegrabDropComplete, WaitingForRegrabNewMoveable, - RegrabFailed, GrabFailed, } interface DefaultHandState { - activeInterface: ActiveInterface; + activeGrab: ActiveInterface; + activePanel: ActiveInterface; grabberFromGrabbable?: AvNodeTransform; state: GrabberState; regrabTarget?: EndpointAddr; @@ -35,8 +36,9 @@ interface DefaultHandState class DefaultHand extends React.Component< DefaultHandProps, DefaultHandState > { - private actionListenerHandle = 0; + private grabListenerHandle = 0; private containerComponent = new SimpleContainerComponent(); + private grabPressed = false; constructor( props: any ) { @@ -44,21 +46,115 @@ class DefaultHand extends React.Component< DefaultHandProps, DefaultHandState > this.state = { - activeInterface: null, + activeGrab: null, + activePanel: null, state: GrabberState.Idle, }; - this.actionListenerHandle = AvGadget.instance().listenForActionStateWithComponent( this.props.hand, - EAction.B, this ); + this.grabListenerHandle = AvGadget.instance().listenForActionState( EAction.Grab, this.props.hand, + this.onGrabPressed, this.onGrabReleased ); } componentWillUnmount() { - AvGadget.instance().unlistenForActionState( this.actionListenerHandle ); + AvGadget.instance().unlistenForActionState( this.grabListenerHandle ); } @bind - private onGrabStart( activeInterface: ActiveInterface ) + private async onGrabPressed() + { + if( this.grabPressed ) + { + console.log( "DUPLICATE GRAB PRESSED!" ); + return; + } + + this.grabPressed = true; + + if( this.state.activeGrab ) + { + this.setState( { state: GrabberState.Grabbing } ); + let res = await this.state.activeGrab.lock(); + if( res != InterfaceLockResult.Success ) + { + console.log( `Fail to lock when grabbing ${ InterfaceLockResult[ res ] }` ); + } + + // check active interface again again because we might have lost it while awaiting + if( this.state.activeGrab ) + { + this.state.activeGrab.sendEvent( { type: GrabRequestType.SetGrabber } as GrabRequest ); + this.setState( { grabberFromGrabbable: this.state.activeGrab.selfFromPeer } ); + } + } + else if( this.state.activePanel ) + { + await this.state.activePanel.lock(); + this.state.activePanel?.sendEvent( { type: PanelRequestType.Down } as PanelRequest ); + } + } + + @bind + private async onGrabReleased() + { + if( !this.grabPressed ) + { + console.log( "DUPLICATE GRAB RELEASED!" ); + } + + this.grabPressed = false; + + if( this.state.activeGrab ) + { + switch( this.state.state ) + { + case GrabberState.GrabFailed: + if( this.state.activeGrab ) + { + this.setState( { state: GrabberState.Highlight } ); + } + else + { + this.setState( { state: GrabberState.Idle } ); + } + break; + + case GrabberState.Grabbing: + // we need to wait here to make sure the moveable on the other + // end has a good transform relative to its new container when + // we unlock below. + await this.state.activeGrab.sendEvent( { type: GrabRequestType.DropYourself } as GrabRequest ); + this.setState( { state: GrabberState.WaitingForDropComplete } ); + break; + + case GrabberState.LostGrab: + this.state.activeGrab.unlock(); + this.setState( { grabberFromGrabbable: null, state: GrabberState.Idle } ); + break; + + case GrabberState.WaitingForRegrab: + // The user let the grab go mid-regrab. We'll just drop when the new moveable comes in + break; + + case GrabberState.GrabReleased: + this.state.activeGrab.unlock(); + this.setState( { state: GrabberState.Highlight } ); + break; + + default: + // other states shouldn't get here + console.log( `Unexpected grabber state ${ GrabberState[ this.state.state ] } on grab release` ); + } + } + else if( this.state.activePanel ) + { + this.state.activePanel.sendEvent( { type: PanelRequestType.Up } as PanelRequest ); + this.state.activePanel.unlock(); + } + } + + @bind + private async onGrabStart( activeInterface: ActiveInterface ) { if( this.state.state == GrabberState.WaitingForRegrabNewMoveable ) { @@ -75,61 +171,30 @@ class DefaultHand extends React.Component< DefaultHandProps, DefaultHandState > grabberFromRegrabTarget: null, regrabTarget: null, } ); + let res = await activeInterface.lock(); + if( res != InterfaceLockResult.AlreadyLocked ) + { + console.log( "How did we get here without having the new thing locked?" ); + } + activeInterface.sendEvent( { type: GrabRequestType.SetGrabber } as GrabRequest ); + + if( !this.grabPressed ) + { + // the user released the grab while we were acquiring our new moveable. So we need to + // drop it immediately. + await activeInterface.sendEvent( { type: GrabRequestType.DropYourself } as GrabRequest ); + this.setState( { state: GrabberState.WaitingForDropComplete } ); + } } else { AvGadget.instance().sendHapticEvent(activeInterface.self, 0.7, 1, 0 ); + this.setState( { state: GrabberState.Highlight } ); } + console.log( `setting activeInterface to ${ endpointAddrToString( activeInterface.peer ) }`) + this.setState( { activeGrab: activeInterface } ); - let listenHandle = AvGadget.instance().listenForActionState( EAction.Grab, this.props.hand, - async () => - { - this.setState( { state: GrabberState.Grabbing } ); - // A was pressed - await activeInterface.lock(); - activeInterface.sendEvent( { type: GrabRequestType.SetGrabber } as GrabRequest ); - this.setState( { grabberFromGrabbable: activeInterface.selfFromPeer } ); - }, - async () => - { - // A was released - switch( this.state.state ) - { - case GrabberState.GrabFailed: - if( this.state.activeInterface ) - { - this.setState( { state: GrabberState.Highlight } ); - } - else - { - this.setState( { state: GrabberState.Idle } ); - } - break; - - case GrabberState.Grabbing: - // we need to wait here to make sure the moveable on the other - // end has a good transform relative to its new container when - // we unlock below. - await activeInterface.sendEvent( { type: GrabRequestType.DropYourself } as GrabRequest ); - this.setState( { state: GrabberState.WaitingForDropComplete } ); - break; - - case GrabberState.LostGrab: - AvGadget.instance().unlistenForActionState( listenHandle ); - activeInterface.unlock(); - this.setState( { grabberFromGrabbable: null, state: GrabberState.Idle } ); - break; - - case GrabberState.WaitingForRegrab: - this.setState( { state: GrabberState.RegrabFailed } ); - break; - - default: - // other states shouldn't get here - console.log( `Unexpected grabber state ${ GrabberState[ this.state.state ] } on grab release` ); - } - } ); activeInterface.onEvent( async ( event: GrabRequest ) => @@ -142,12 +207,12 @@ class DefaultHand extends React.Component< DefaultHandProps, DefaultHandState > { case GrabberState.WaitingForDropComplete: activeInterface.unlock(); - this.setState( { state: GrabberState.Highlight } ); + this.setState( { state: GrabberState.Highlight, grabberFromGrabbable: null } ); break; case GrabberState.WaitingForRegrabDropComplete: let resPromise = activeInterface.relock( this.state.regrabTarget ); - this.setState( { state: GrabberState.WaitingForRegrab } ); + this.setState( { state: GrabberState.WaitingForRegrab, grabberFromGrabbable: null } ); let res = await resPromise; if( res != InterfaceLockResult.Success ) { @@ -187,6 +252,15 @@ class DefaultHand extends React.Component< DefaultHandProps, DefaultHandState > } } break; + + case GrabRequestType.ReleaseMe: + { + if( this.state.state == GrabberState.Grabbing ) + { + this.setState( { state: GrabberState.GrabReleased, grabberFromGrabbable: null } ); + } + } + break; } } ); @@ -194,14 +268,14 @@ class DefaultHand extends React.Component< DefaultHandProps, DefaultHandState > { switch( this.state.state ) { + case GrabberState.GrabReleased: case GrabberState.Grabbing: this.setState( { state: GrabberState.LostGrab } ); break; case GrabberState.Highlight: this.setState( { state: GrabberState.Idle } ); - AvGadget.instance().unlistenForActionState( listenHandle ); - AvGadget.instance().sendHapticEvent(activeInterface.self, 0.3, 1, 0 ); + AvGadget.instance().sendHapticEvent( activeInterface.self, 0.3, 1, 0 ); break; case GrabberState.WaitingForRegrab: @@ -215,10 +289,11 @@ class DefaultHand extends React.Component< DefaultHandProps, DefaultHandState > console.log( `Unexpected grabber state ${ GrabberState[ this.state.state ] } on interface end` ); } - this.setState( { activeInterface: null } ); + console.log( `unsetting activeInterface from ${ endpointAddrToString( this.state.activeGrab?.peer ) }`) + + this.setState( { activeGrab: null } ); } ); - this.setState( { activeInterface } ); } @bind @@ -226,35 +301,29 @@ class DefaultHand extends React.Component< DefaultHandProps, DefaultHandState > { AvGadget.instance().sendHapticEvent( activeInterface.self, 0.7, 1, 0 ); - let listenHandle = AvGadget.instance().listenForActionState( EAction.Grab, this.props.hand, - async () => - { - // Grab was pressed - await activeInterface.lock(); - activeInterface.sendEvent( { type: PanelRequestType.Down } as PanelRequest ); - }, - async () => - { - // Grab was released - activeInterface.sendEvent( { type: PanelRequestType.Up } as PanelRequest ); - activeInterface.unlock(); - } ); - activeInterface.onEnded( () => { - AvGadget.instance().unlistenForActionState( listenHandle ); - this.setState( { activeInterface: null } ); + this.setState( { activePanel: null } ); AvGadget.instance().sendHapticEvent(activeInterface.self, 0.3, 1, 0 ); } ); - this.setState( { activeInterface } ); + this.setState( { activePanel: activeInterface } ); + } + + public renderDebug() + { + return
+
{ GrabberState[ this.state.state ] }
+
ActiveInterface: { this.state.activeGrab ? endpointAddrToString( this.state.activeGrab.peer ): "none" }
+
Transform: { this.state.grabberFromGrabbable ? JSON.stringify( this.state.grabberFromGrabbable ): "none" }
+
} public render() { let modelColor = "#222288FF"; let highlightColor = "#FF0000FF"; - if( this.state.activeInterface ) + if( this.state.activeGrab ) { modelColor = highlightColor; } @@ -296,7 +365,15 @@ class DefaultHand extends React.Component< DefaultHandProps, DefaultHandState > radius: 0.01, }; + let child: EndpointAddr = null; + if( this.state.activeGrab && this.state.grabberFromGrabbable + && this.state.state != GrabberState.GrabReleased ) + { + child = this.state.activeGrab.peer; + } + return ( + <> {/* ] } volume={ k_grabberVolume } > { - this.state.activeInterface && this.state.grabberFromGrabbable + child && - + } + { this.renderDebug() } + ); } } @@ -322,6 +401,8 @@ class DefaultHand extends React.Component< DefaultHandProps, DefaultHandState > class DefaultHands extends React.Component< {}, {} > { private containerComponent = new SimpleContainerComponent(); + private leftRef = React.createRef(); + private rightRef = React.createRef(); public render() { @@ -338,8 +419,8 @@ class DefaultHands extends React.Component< {}, {} > return ( <> - - + + {/* @@ -349,6 +430,12 @@ class DefaultHands extends React.Component< {}, {} > volume={ { type: EVolumeType.Infinite } }> + {/* + + + + + */} ); }