'From NICT-1.0 of 16 December 2004 [latest update: #45] on 14 January 2005 at 5:36:43 pm'! "Change Set: BeaconAndGhostViewer Date: 14 January 2005 Author: Peter Moore This update adds a TBeacon for marking interesting points in a space (lots of work to do still) and the first cut at a type of filter object. It allows you to view ghost objects through the filter that are not a part of the space itself, although you can position them as though they were."! TGroup subclass: #TBeacon instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Croquet-Beacons'! !TBeacon commentStamp: 'pbm 1/14/2005 17:16' prior: 0! You can use a TBeacon to mark a place of interest in a space. Eventually, you will be able to drop a beacon into a space, reposition it, and give it a name. Then you can access a list of all your beacons and by selecting one be transported to it.! TPortal subclass: #TGhostViewer instanceVariableNames: 'ghostFrame ' classVariableNames: '' poolDictionaries: '' category: 'Croquet-Beacons'! !TGhostViewer commentStamp: 'pbm 1/14/2005 17:23' prior: 0! TGhostViewer is an example of a filter object. You can use it to create new "ghost" object that can only be seen through the viewer. The objects will not be a part of the TSpace frame hierarcy, but are instead children of the TGhostViewer object. However, by moving the view around it appears to the ownder of the viewer that the objects exist in the space. Try it out using a TBeacon by evaluating the following code in a workspace: viewer _ TGhostViewer meta new. win _ CroquetGlobals theTeapotMorph activeCamera avatar makeWindowInFront. win meta contents: viewer. viewer addGhostFrame: (TDragger new contents: (TBeacon new)). ! !TBeacon methodsFor: 'as yet unclassified' stamp: 'pbm 1/14/2005 17:11'! loadModel | tframe | tframe := CroquetData loadURL: 'http://www.reed.com/TeaLand/spaces/beacon.tea'. tframe ifNil:[ tframe _ (TLoad3DSMax new initializeWithFileName: (FileDirectory pathFrom: {FileDirectory default pathName. 'Content'. 'Beacon'. 'beacon.ASE'}) ) frame. tframe translationX: 0 y:0 z:0. tframe collapse. tframe boundsDepth: 2. tframe initBounds. tframe transparency: 0.5. TExporter export: tframe asBinary:'http://www.reed.com/TeaLand/spaces/radio.tea'. ]. self addChild: tframe! ! !TBeacon methodsFor: 'initialize' stamp: 'pbm 1/14/2005 13:02'! initialize super initialize. self loadModel. self startStepping.! ! !TBeacon methodsFor: 'stepping' stamp: 'pbm 1/14/2005 17:07'! stepAt: msecs self addRotationAroundY: 2.! ! !TBeacon methodsFor: 'stepping' stamp: 'pbm 1/14/2005 13:01'! wantsSteps ^ true! ! !TGhostViewer methodsFor: 'render' stamp: 'pbm 1/14/2005 14:30'! render: ogl depth: depth | trans m2 toSpace gt containerPortal portalClip color clipPlanes renderedObjects ac saveSelected saveDoSelect saveMinDistance portalDistance saveForceWire top portalPoint | " This method is the recursive portal renderer. enter refers to the renderer entering the portal. If you want the camera entering check out TPortal>>testEnter: ac." renderedObjects _ 1. "We are also rendering the portal." " ------- We keep track of which portal we are currently working in to avoid a tight recursion error,where the portal re-enters itself to render. It is (somewhat) OK to re-enter from another portal. This is equivalent to a mirror in a mirror situation. Here we need to keep a count of the re-entries.------" toSpace _ toPortal root. toSpace = toPortal ifTrue:[ top _ self. toSpace _ self root. ] "The portal is not installed on the other side!!" ifFalse:[top _ toPortal]. containerPortal _ ogl currentPortal. ogl currentPortal: self. " ------ For rendering the portal ONLY - we turn off depth testing. We just want to write into the stencil buffer here and set the color of the toSpace. Depth testing is turned back on before we recurse into the connected portal.------" gt _ self globalTransform. depth = 1 ifTrue:[ogl glEnable: GLStencilTest.]. ogl glPushMatrix. ogl glMultMatrixf: gt transposed. ogl glDisable: GLDepthTest. " ------ Paint the portal with the color of the TSpace ------ " ogl glDisable: GLLighting. color _ B3DVector3 x: toSpace color red y: toSpace color green z: toSpace color blue. ogl glColor3fv: color. ogl glStencilFunc:GLEqual with:depth-1 with: -1. ogl glStencilOp:GLKeep with:GLKeep with:GLIncr. " ------ Render the stencil here. ------ " saveForceWire _ ogl forceWire. ogl forceWire:false. self renderPortal: ogl. ogl forceWire: saveForceWire. ogl glStencilFunc: GLEqual with: depth with: -1. ogl glStencilOp: GLKeep with: GLKeep with: GLKeep. ogl glEnable: GLDepthTest. ogl glEnable: GLLighting. ogl glPopMatrix. ogl glPushMatrix. " m1 _ self globalTransform." top isPortal ifTrue:[m2 _ top globalMatrixOut.] ifFalse:[m2 _ top globalTransform.]. " ------- For simplicity - and because I can't think of a good reason not to do this - if a portal points back to itself, it is considered to be a mirror. Note that we flip the directions of the polyon tests as well. This is restored at the end. We are inverting the x-coordinate of the matrix here. ------" self = top ifTrue:[ m2 a11: m2 a11 negated. m2 a21: m2 a21 negated. m2 a31: m2 a31 negated. ogl flipFace.]. ac _ ogl camera. portalClip _ac portalClip. ac portalClip: m2. " ------ This math is simply: m1 = trans*m2 m1 * m2**-1 = trans * m2 * m2**-1 m1 * m2**-1 = trans. We then replace the transform matrix of the TCamera with the appropriate transform, which places the TCamera into the correct position in the new space.------" clipPlanes _ ac clipPlanes. ac clipPlanes: (self initClipPlanes: ac globalTransform mirror: ogl isMirror). self = top ifTrue:[ ogl mirrorFlip.]. trans _ m2 composeWith: gt orthoNormInverse. ac globalTransform: (trans composeWith: ac globalTransform). ac pointer ifNotNil:[ saveSelected _ ac pointer copiedSelection. saveMinDistance _ ac pointer minDistance. saveDoSelect _ ac pointer doSelect. (ac pointer pointerPick: self boundSphere) ifTrue:[ portalDistance _ ac pointer selectedDistance. ac pointer minDistance: portalDistance. portalPoint _ ac pointer selectedPoint. ac pointer selected: saveSelected. "restore the current selections" ac pointer selectedPortal: self point: portalPoint. "if we picked a portal" ] ifFalse:[ ac pointer isDown ifTrue:[ ac pointer selectedPortal = self ifTrue:[ (ac pointer frame: self pickPlane: (B3DVector3 new) normal: outVector) ifTrue:[ portalPoint _ ac pointer selectedPoint. ac pointer selected: saveSelected. ac pointer selectedPortalPoint: portalPoint. ]. ]. ]. portalDistance _ Float infinity. ac pointer doSelect: false. ac pointer selected: saveSelected. ]. ac pointer globalTransform: (ac globalTransform composeWith: ac pointer localTransform). ]. renderedObjects _ renderedObjects + (toSpace renderSpace: ogl port: top depth: depth+1 ghostFrame: ghostFrame). "<------ Render TSpace here!! -------" ac clipPlanes: clipPlanes. self = top ifTrue:[ogl flipFace. ogl mirrorFlip.]. ogl glPopMatrix. " ------ Just in case we jumped into a portal, restore the stencil depth ------" ogl glStencilFunc: GLLequal with: depth-1 with: -1. ogl glStencilOp: GLKeep with: GLKeep with: GLKeep. "------ Render the front face invisibly to set the z-buffer to ensure nothing bleeds into the portal space from our space. ------" ogl glPushMatrix. ogl glPolygonOffset: 2.1 with: 4.0. ogl glEnable: GLPolygonOffsetFill. ogl glColorMask: 0 with: 0 with: 0 with: 0. ogl glMultMatrixf: gt transposed. ogl forceWire: false. self renderPortal: ogl. ogl forceWire: saveForceWire. ogl glColorMask:GLTrue with: GLTrue with:GLTrue with:GLTrue. ogl glDisable: GLPolygonOffsetFill. ogl glPopMatrix. depth = 1 ifTrue:[ogl glDisable: GLStencilTest.]. ogl currentPortal: containerPortal. ac portalClip: portalClip. ac pointer ifNotNil:[ ac pointer doSelect: saveDoSelect. ac pointer minDistance: saveMinDistance. ac pointer maxDistance: (ac pointer maxDistance min: portalDistance). ]. ^ renderedObjects. ! ! !TGhostViewer methodsFor: 'accessing' stamp: 'pbm 1/14/2005 14:29'! ghostFrame ^ ghostFrame ifNil: [ ghostFrame := TGroup new ]! ! !TGhostViewer methodsFor: 'initialize' stamp: 'pbm 1/14/2005 14:35'! initialize | view | super initialize. view _ TGroup new. self addChild: view. self linkPortal: view. ghostFrame := nil.! ! !TGhostViewer methodsFor: 'as yet unclassified' stamp: 'pbm 1/14/2005 14:30'! addGhostFrame: tframe self ghostFrame addChild: tframe.! ! !TGhostViewer methodsFor: 'as yet unclassified' stamp: 'pbm 1/14/2005 14:33'! reentrant "this portal is typically used such that the out portal frame is congruent to the in portal frame, hence we should never recurse here" ^ false.! ! !TGhostViewer methodsFor: 'as yet unclassified' stamp: 'pbm 1/14/2005 14:30'! removeGhostFrame: tframe ghostFrame ifNotNil: [ ghostFrame frameChildren ifNotNil: [ ghostFrame removeChild: tframe. ]. ]. ghostFrame frameChildren ifNil: [ ghostFrame := nil. ].! ! "Postscript: Download the beacon model." url := 'http://hedgehog.software.umn.edu/croquet/NICT/install/Beacon.zip'. (self confirm: ('This update needs to download some additional content from\ \', url, '\ \Do you want me to download and install it now?\', '(you may say ''no'' if this is an old update)') withCRs) ifFalse:[ self inform: ('Okay, it is your choice. Just in case, here is the url again:\ \', url,'\ \') withCRs. ^self]. dirPath := #('Content' 'Beacon'). Utilities informUserDuring:[:bar| bar value: 'Downloading ', url. Cursor wait showWhile:[data := url asUrl retrieveContents]. archive := ZipArchive new readFrom: data contentStream. dir := dirPath inject: FileDirectory default into:[:base :part| base directoryNamed: part]. dir assureExistence. Cursor write showWhile:[archive extractAllTo: dir informing: bar overwrite: true] ]. !