GGSceneImpl.mesa
Contents: The procedural interface to the Gargoyle object modeler.
Copyright Ó 1985, 1988, 1992 by Xerox Corporation. All rights reserved.
Stone, August 5, 1985 4:13:18 pm PDT
Pier, January 14, 1993 1:25 pm PST
Kurlander August 28, 1986 6:15:29 pm PDT
Bier, February 5, 1993 3:27 pm PST
DIRECTORY
BasicTime, GGBasicTypes, GGBoundBox, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGScene, GGSceneType, GGSegmentTypes, GGSelect, GGSlice, GGSliceOps, GGTraj, GGUtility, Imager, ImagerPath, Rope, TextNode;
GGSceneImpl:
CEDAR
MONITOR
IMPORTS BasicTime, GGBoundBox, GGOutline, GGParent, GGScene, GGSelect, GGSlice, GGSliceOps, GGTraj, GGUtility, Imager
EXPORTS GGScene, GGModelTypes = BEGIN
BoundBox: TYPE = GGModelTypes.BoundBox;
BoundBoxGenerator: TYPE = GGModelTypes.BoundBoxGenerator;
BoundBoxGeneratorObj: TYPE = GGModelTypes.BoundBoxGeneratorObj;
Camera: TYPE = GGModelTypes.Camera;
CameraObj: TYPE = GGModelTypes.CameraObj;
Color: TYPE = Imager.Color;
CurveType: TYPE = {line, bezier, conic};
FeatureCycler: TYPE = GGInterfaceTypes.FeatureCycler;
GGData: TYPE = GGInterfaceTypes.GGData;
Joint: TYPE = GGSegmentTypes.Joint;
JointGenerator: TYPE = GGModelTypes.JointGenerator;
JointGeneratorObj: TYPE = GGModelTypes.JointGeneratorObj;
JointObj: TYPE = GGSegmentTypes.JointObj;
OutlineData: TYPE = GGOutline.OutlineData;
Point: TYPE = GGBasicTypes.Point;
Scene: TYPE = GGModelTypes.Scene;
SceneObj: PUBLIC TYPE = GGSceneType.SceneObj; -- export of opaque type
SelectedData: PUBLIC TYPE = GGSceneType.SelectedData; -- export of opaque type
SelectionClass: TYPE = GGSegmentTypes.SelectionClass;
Segment: TYPE = GGModelTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
SegmentObj: TYPE = GGSegmentTypes.SegmentObj;
Slice: TYPE = GGModelTypes.Slice;
SliceParts: TYPE = GGModelTypes.SliceParts;
SlicePartsWalkProc: TYPE = GGModelTypes.SlicePartsWalkProc;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator;
SliceDescriptorWalkProc: TYPE = GGModelTypes.SliceDescriptorWalkProc;
SliceGenerator: TYPE = GGModelTypes.SliceGenerator;
SliceGeneratorObj: TYPE = GGModelTypes.SliceGeneratorObj;
SliceWalkProc: TYPE = GGScene.SliceWalkProc;
Traj: TYPE = GGModelTypes.Traj;
TrajData: TYPE = GGModelTypes.TrajData;
TrajParts: TYPE = GGModelTypes.TrajParts;
TrajEnd: TYPE = GGModelTypes.TrajEnd; -- {lo, hi};
Vector: TYPE = GGBasicTypes.Vector;
WalkLevel: TYPE = GGModelTypes.WalkLevel;
The scene object has a list of entities, a "finger" REF (ptr) to the end of the list for quick appends, and a BOOL which is TRUE when ptr in fact points to the end. ptr MUST BE invalidated by setting ptrValid ← FALSE whenever a remove or delete is done from the entities. Any use of ptr must check ptrValid and recalculate its value if it is invalid. The only place that happens in here is in AppendEntity.
entities are kept in back to front order so that the list can be painted with a single traversal.
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = CODE;
Creating, Modifying Scenes
The list scene.entities is kept in back to front order. Hence, newly added objects are usually added to the end of the list. This list may be mutated freely. Copies of it are made when needed.
CreateScene:
PUBLIC
PROC [entities:
LIST
OF Slice ¬
NIL]
RETURNS [scene: Scene] = {
scene ¬ NEW[SceneObj ¬ [entities: entities, prioritiesValid: FALSE, backgroundColor: Imager.white] ];
scene.lastEditTime ¬ BasicTime.ExtendedNow[];
};
SetScene:
PUBLIC
PROC [from: Scene, to: Scene] = {
to2: REF SceneObj ¬ NARROW[to];
from1: REF SceneObj ¬ NARROW[from];
to2 ¬ from1;
};
CreateDefaultCamera:
PUBLIC
PROC []
RETURNS [camera: Camera] = {
camera ¬ NEW[CameraObj];
};
AddSlice:
PUBLIC
PROC [scene: Scene, slice: Slice, priority:
INT ¬ -1] = {
Add the given slice to the scene with the given priority (the higher the priority number, the closer the slice is to the front of the scene). If priority is -1, the slice will be the front-most entity. If the priority given is too high, we proceed as if priority = -1.
IF slice=NIL THEN RETURN;
slice.parent ¬ NIL;
IF priority = -1 OR TopPriority[scene]<priority THEN AppendSlice[scene, slice]
ELSE
IF priority = 0
THEN {
scene.ptrValid ¬ FALSE;
scene.prioritiesValid ¬ FALSE;
scene.entities ¬ CONS[slice, scene.entities];
}
ELSE PutBehind[scene, GetAtPriority[scene, priority], LIST[slice]];
};
AddSlices:
PUBLIC
PROC [scene: Scene, slices:
LIST
OF Slice, priority:
INT ¬ -1] = {
Adds the slices, assumed to be in back to front order, to the scene. IF priority = -1, they become the frontmost entities. Otherwise, the specified priority becomes the priority of the first entity in the list, if possible. If the priority given is too high, we proceed as if priority = -1.
IF slices=NIL THEN RETURN;
FOR list:
LIST
OF Slice ¬ slices, list.rest
UNTIL list =
NIL
DO
list.first.parent ¬ NIL;
ENDLOOP;
IF priority = -1
OR TopPriority[scene]<priority
THEN {
FOR list:
LIST
OF Slice ¬ slices, list.rest
UNTIL list =
NIL
DO
AppendSlice[scene, list.first];
ENDLOOP;
}
ELSE
IF priority = 0
THEN {
scene.ptrValid ¬ FALSE;
scene.prioritiesValid ¬ FALSE;
scene.entities ¬ GGUtility.AppendSliceList[slices, scene.entities]; -- COPIES first argument
}
ELSE {
Copy incoming list to avoid mutations by PutBehind
copiedSlices: LIST OF Slice ¬ GGUtility.CopySliceList[slices];
PutBehind[scene, GetAtPriority[scene, priority], copiedSlices];
};
};
AddHole:
PUBLIC
PROC [outline: Slice, hole: Slice, scene: Scene]
RETURNS [holier: Slice] = {
holeOutline: Slice ¬ GGParent.GetParent[hole];
priority: INT ¬ GetPriority[scene: scene, slice: outline];
IF holeOutline = outline THEN ERROR;
holier ¬ GGOutline.AddChild[outline, hole];
DeleteSlice[scene, IF holeOutline=NIL THEN hole ELSE holeOutline];
DeleteSlice[scene, outline];
AddSlice[scene, holier, priority];
};
AppendHoles:
PUBLIC
PROC [outline: Slice, holes:
LIST
OF Slice] = {
holes should not have parents. May create obsolete sequences, so be careful.
outlineData: OutlineData ¬ NARROW[outline.data];
FOR holeList:
LIST
OF Slice ¬ holes, holeList.rest
UNTIL holeList=
NIL
DO
outlineData.children ¬ GGUtility.AppendSliceList[outlineData.children, LIST[holeList.first]];
holeList.first.parent ¬ outline;
IF GGSliceOps.GetType[holeList.first]=$Traj THEN GGTraj.SetTrajRole[holeList.first, hole];
ENDLOOP;
GGSlice.KillBoundBox[outline];
};
AppendSlice:
PROC [scene: Scene, slice: Slice] = {
scene.prioritiesValid ¬ FALSE;
IF NOT scene.ptrValid THEN UpdatePtr[scene];
[scene.entities, scene.ptr] ¬ GGUtility.AddSlice[slice, scene.entities, scene.ptr];
};
UpdatePtr:
PROC [scene: Scene] = {
IF scene.entities=NIL THEN scene.ptr ¬ NIL ELSE
FOR list:
LIST
OF Slice ¬ scene.entities, list.rest
UNTIL list=
NIL
DO
scene.ptr ¬ list;
ENDLOOP;
scene.ptrValid ¬ TRUE;
};
SetBackgroundColor:
PUBLIC
PROC [scene: Scene, color: Color] = {
scene.backgroundColor ¬ color;
};
GetBackgroundColor:
PUBLIC
PROC [scene: Scene]
RETURNS [color: Color] = {
color ¬ scene.backgroundColor;
};
SetLastEditedTime:
PUBLIC
PROC [scene: Scene, time: BasicTime.ExtendedGMT] = {
scene.lastEditTime ¬ time;
};
GetLastEditedTime:
PUBLIC
PROC [scene: Scene]
RETURNS [time: BasicTime.ExtendedGMT] = {
time ¬ scene.lastEditTime;
};
Manipulations on Multiple Scenes
CopySelectedParts:
PUBLIC
PROC [fromScene: Scene, toScene: Scene]
RETURNS [newSlices:
LIST
OF Slice ¬
NIL] = {
Copy all parts that are current selectedly in "from" and place the copies in "to" at the frontmost priority.
MakeNewSlices:
PROC [sliceD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
copies: LIST OF Slice ¬ GGSliceOps.Copy[sliceD.slice, sliceD.parts];
FOR list:
LIST
OF Slice ¬ copies, list.rest
UNTIL list =
NIL
DO
list.first.priority ¬ GGScene.GetPriority[fromScene, sliceD.slice];
[newSlices, ptr] ¬ GGUtility.AddSlice[list.first, newSlices, ptr];
ENDLOOP;
};
ptr: LIST OF Slice ¬ NIL;
[] ¬ GGScene.WalkSelectedSlices[fromScene, first, MakeNewSlices, normal];
newSlices ¬ GGUtility.SortSliceListByPriority[newSlices]; -- preserves order of equal elements
GGSelect.DeselectAll[toScene, normal];
Add all of the new slices to the "to" scene and select them.
FOR sliceList:
LIST
OF Slice ¬ newSlices, sliceList.rest
UNTIL sliceList =
NIL
DO
GGScene.AddSlice[toScene, sliceList.first, -1];
GGSelect.SelectEntireSlice[sliceList.first, toScene, normal];
ENDLOOP;
};
UpdatePriorities:
PROC [scene: Scene] = {
DoUpdate:
PROC [slice: Slice]
RETURNS [done:
BOOL ¬
FALSE] = {
slice.priority ¬ index;
index ¬ index + 1;
};
index: NAT ¬ 0;
[] ¬ GGScene.WalkSlices[scene, first, DoUpdate];
scene.prioritiesValid ¬ TRUE;
};
GetPriority:
PUBLIC
PROC [scene: Scene, slice: Slice]
RETURNS [priority:
INT] = {
If returned priority is 0, the slice is the back-most entity.
IF NOT scene.prioritiesValid THEN UpdatePriorities[scene];
priority ¬ slice.priority;
};
TopPriority:
PUBLIC
PROC [scene: Scene]
RETURNS [priority:
INT] = {
Returns the priority of the frontmost position in the scene.
IF NOT scene.prioritiesValid THEN UpdatePriorities[scene];
IF NOT scene.ptrValid THEN UpdatePtr[scene];
RETURN[IF scene.ptr=NIL THEN 0 ELSE scene.ptr.first.priority];
};
UpOne:
PUBLIC
PROC [scene: Scene, slice: Slice] = {
Moves the named slice one step closer to the front in the priority order.
ExchangeWithNextEntity:
PROC [scene: Scene, slice: Slice] = {
previous, next: LIST OF Slice ¬ NIL;
FOR list:
LIST
OF Slice ¬ scene.entities, list.rest
UNTIL list =
NIL
DO
IF list.first=slice
THEN {
-- exchange this entity with its next neighbor
next ¬ list.rest;
IF next=NIL THEN RETURN; -- entity already at end (i.e. top) of list
list.rest ¬ next.rest; next.rest ¬ list;
IF previous#NIL THEN previous.rest ¬ next ELSE scene.entities ¬ next;
}
ELSE previous ¬ list;
ENDLOOP;
};
scene.ptrValid ¬ scene.prioritiesValid ¬ FALSE;
ExchangeWithNextEntity[scene, slice];
};
PutInFront:
PUBLIC
PROC [scene: Scene, slice: Slice, slices:
LIST
OF Slice] = {
Destructively splices slices into scene
IF slice#
NIL
AND slices#
NIL
THEN {
beforeEnt, ent, afterEnt, slicesRest: LIST OF Slice;
found: BOOL ¬ FALSE;
[beforeEnt, ent, afterEnt, found] ¬ GGUtility.FindSliceAndNeighbors[slice, scene.entities];
IF found
THEN {
IF ent.first#slice THEN ERROR; -- debugging test
scene.ptrValid ¬ FALSE;
scene.prioritiesValid ¬ FALSE;
ent.rest ¬ slices; -- ent is really part of scene.entities. Skip over slice and then Splice in new slices.
FOR sList:
LIST
OF Slice ¬ slices, sList.rest
UNTIL sList=
NIL
DO
slicesRest ¬ sList;
ENDLOOP;
slicesRest.rest ¬ afterEnt;
};
};
};
DownOne:
PUBLIC
PROC [scene: Scene, slice: Slice] = {
Moves the named slice one step closer to the back in the priority order.
ExchangeWithPreviousEntity:
PROC [scene: Scene, slice: Slice] = {
preprev, previous: LIST OF Slice ¬ NIL;
FOR list:
LIST
OF Slice ¬ scene.entities, list.rest
UNTIL list=
NIL
DO
IF list.first=slice
THEN {
-- exchange this entity with its previous neighbor
SELECT
TRUE
FROM
preprev=NIL AND previous=NIL => RETURN; -- entity already on bottom
preprev=
NIL
AND previous#
NIL => {
-- special case. Second from bottom
previous.rest ¬ list.rest; list.rest ¬ previous; scene.entities ¬ list; RETURN;
};
preprev#NIL AND previous=NIL => ERROR; -- Assert: can't happen
preprev#
NIL
AND previous#
NIL => {
-- usual case. Exchange list elements
previous.rest ¬ list.rest; list.rest ¬ previous; preprev.rest ¬ list; RETURN;
};
ENDCASE => ERROR; -- Assert: can't happen
}
ELSE { preprev ¬ previous; previous ¬ list; };
ENDLOOP;
};
scene.ptrValid ¬ scene.prioritiesValid ¬ FALSE;
ExchangeWithPreviousEntity[scene, slice];
};
PutBehind:
PUBLIC
PROC [scene: Scene, slice: Slice, slices:
LIST
OF Slice] = {
Destructively splices slices into scene
IF slice#
NIL
AND slices#
NIL
THEN {
beforeEnt, ent, afterEnt, slicesRest: LIST OF Slice;
found: BOOL ¬ FALSE;
[beforeEnt, ent, afterEnt, found] ¬ GGUtility.FindSliceAndNeighbors[slice, scene.entities];
IF found
THEN {
IF ent.first#slice THEN ERROR; -- debugging test
scene.ptrValid ¬ FALSE;
scene.prioritiesValid ¬ FALSE;
IF beforeEnt#NIL THEN beforeEnt.rest ¬ slices ELSE scene.entities ¬ slices; -- beforeEnt is really part of scene.entities. Splice in new slices before the existing slice
FOR sList:
LIST
OF Slice ¬ slices, sList.rest
UNTIL sList=
NIL
DO
slicesRest ¬ sList;
ENDLOOP;
slicesRest.rest ¬ ent; -- add on the original slice and the remaining entities
};
};
};
ReplaceSlice:
PUBLIC
PROC [scene: Scene, slice: Slice, slices:
LIST
OF Slice] = {
Removes slice from scene and puts back-to-front slices in its place.
Destructively splices slices into scene
beforeEnt, ent, afterEnt, slicesRest: LIST OF Slice;
found: BOOL ¬ FALSE;
IF slice#
NIL
AND slices#
NIL
THEN {
[beforeEnt, ent, afterEnt, found] ¬ GGUtility.FindSliceAndNeighbors[slice, scene.entities];
IF found
THEN {
IF ent.first#slice THEN ERROR; -- debugging test
scene.ptrValid ¬ FALSE;
scene.prioritiesValid ¬ FALSE;
IF beforeEnt#NIL THEN beforeEnt.rest ¬ slices ELSE scene.entities ¬ slices; -- beforeEnt is really part of scene.entities. Splice in new slices before the existing slice
FOR sList:
LIST
OF Slice ¬ slices, sList.rest
UNTIL sList=
NIL
DO
slicesRest ¬ sList;
ENDLOOP;
slicesRest.rest ¬ afterEnt;
};
};
};
PutAtPriority:
PUBLIC
PROC [scene: Scene, slice: Slice, priority:
INT ¬ -1] = {
If priority is 0, the slice will be the back-most entity. If priority is -1, the slice will be the front-most entity. If the priority given is too high, we proceed as if priority = -1.
RemoveSlice[scene, slice];
AddSlice[scene, slice, priority];
};
GetAtPriority:
PUBLIC
PROC [scene: Scene, priority:
INT]
RETURNS [slice: Slice] = {
IF NOT scene.prioritiesValid THEN UpdatePriorities[scene];
IF priority = -1
THEN {
-- special case: frontmost
IF NOT scene.ptrValid THEN UpdatePtr[scene];
RETURN[scene.ptr.first];
}
ELSE IF priority = 0 THEN RETURN[scene.entities.first] -- special case: rear-most
ELSE {
-- somewhere in the middle
count: INT ¬ 1;
FOR sliceList:
LIST
OF Slice ¬ scene.entities.rest, sliceList.rest
UNTIL sliceList=
NIL
DO
IF count=priority THEN RETURN[sliceList.first];
count ¬ count+1;
ENDLOOP;
or off the scale
IF NOT scene.ptrValid THEN UpdatePtr[scene];
RETURN[scene.ptr.first];
};
};
DeleteAtPriority:
PUBLIC
PROC [scene: Scene, priority:
INT]
RETURNS [deleted: Slice] = {
deleted ¬ GetAtPriority[scene, priority];
DeleteSlice[scene, deleted];
};
RemoveSlice:
PUBLIC
PROC [scene: Scene, slice: Slice] = {
scene.ptrValid ¬ FALSE;
scene.prioritiesValid ¬ FALSE;
scene.entities ¬ GGUtility.DeleteSliceFromList[slice, scene.entities];
};
DeleteSlice:
PUBLIC
PROC [scene: Scene, slice: Slice] = {
scene.ptrValid ¬ FALSE;
scene.prioritiesValid ¬ FALSE;
GGSelect.DeselectEntityAllClasses[slice, scene];
scene.entities ¬ GGUtility.DeleteSliceFromList[slice, scene.entities];
};
DeleteSequence:
PUBLIC
PROC [seq: SliceDescriptor, scene: Scene]
RETURNS [oldOutline: Slice, newOutlines:
LIST
OF Slice] = {
priority: INT;
traj: Slice ¬ seq.slice;
smallerOutline: Slice;
oldOutline ¬ GGParent.GetParent[traj];
GGOutline.SaveSelectionsInOutlineAllClasses[oldOutline];
priority ¬ GGScene.GetPriority[scene, oldOutline];
[smallerOutline, newOutlines] ¬ GGTraj.DeleteSequence[seq];
IF smallerOutline # NIL THEN newOutlines ¬ CONS[smallerOutline, newOutlines];
IF newOutlines#NIL THEN AddSlices[scene, newOutlines, priority];
DeleteSlice[scene, oldOutline];
FOR list:
LIST
OF Slice ¬ newOutlines, list.rest
UNTIL list =
NIL
DO
GGSelect.ReselectSliceAllClasses[list.first, scene];
ENDLOOP;
};
OutlinesFromOutlineExcept:
PROC [outline: Slice, except: Traj]
RETURNS [newOutlines:
LIST
OF Slice] = {
ptr: LIST OF Slice;
newSlice, newOutline: Slice;
children: LIST OF Slice ¬ NARROW[outline.data, OutlineData].children;
[newOutlines, ptr] ¬ GGUtility.StartSliceList[];
FOR childList:
LIST
OF Slice ¬ children, childList.rest
UNTIL childList =
NIL
DO
IF childList.first = except THEN LOOP;
newSlice ¬ GGSliceOps.Copy[childList.first].first;
newOutline ¬ GGOutline.CreateOutline[newSlice, GGSliceOps.GetFillColor[outline, NIL].color];
[newOutlines, ptr] ¬ GGUtility.AddSlice[newOutline, newOutlines, ptr];
ENDLOOP;
};
noGarbage: BOOL ¬ FALSE;
MakeSceneGarbage:
PUBLIC
PROC [scene: Scene] = {
Ruthlessly destroy an entire scene so the Cedar gargbage collector can sweep up.
oldEntities: LIST OF LIST OF Slice;
entities: LIST OF Slice;
IF noGarbage THEN RETURN;
oldEntities ¬ scene.oldEntities;
scene.oldEntities ¬ NIL;
entities ¬ scene.entities;
scene.entities ¬ NIL; -- in case a screen refresh is attempted while we are doing this.
FOR sllist:
LIST
OF
LIST
OF Slice ¬ oldEntities, sllist.rest
UNTIL sllist=
NIL
DO
FOR list:
LIST
OF Slice ¬ sllist.first, list.rest
UNTIL list=
NIL
DO
IF list.first.class#NIL THEN GGSliceOps.Unlink[list.first]; -- if class=NIL, already unlinked
ENDLOOP;
ENDLOOP;
FOR list:
LIST
OF Slice ¬ entities, list.rest
UNTIL list=
NIL
DO
IF list.first.class#NIL THEN GGSliceOps.Unlink[list.first]; -- if class=NIL, already unlinked
ENDLOOP;
};
MergeScenes:
PUBLIC
PROC [back: Scene, front: Scene]
RETURNS [combined: Scene] = {
combined ¬ NEW[SceneObj ¬ [prioritiesValid: FALSE, backgroundColor: back.backgroundColor]];
combined.entities ¬ GGUtility.AppendSliceList[back.entities, front.entities];
combined.selected.normal ¬ GGUtility.AppendSliceDescriptorList[back.selected.normal, front.selected.normal];
combined.selected.hot ¬ GGUtility.AppendSliceDescriptorList[back.selected.hot, front.selected.hot];
combined.selected.active ¬ GGUtility.AppendSliceDescriptorList[back.selected.active, front.selected.active];
combined.selected.match ¬ GGUtility.AppendSliceDescriptorList[back.selected.match, front.selected.match];
};
Browsing the Scene Hierarchy -- Generators
WalkSlices:
PUBLIC
PROC [scene: Scene, level: WalkLevel, walkProc: SliceWalkProc, classType:
ATOM ¬
NIL]
RETURNS [aborted:
BOOL ¬
FALSE] = {
Built-in classes include: $Cluster, $Outline, $Traj, $Box, $Circle, $Text, and $IP. This routine finds all such slices, even within clusters. class=NIL => all classes. Use level=all to walk every slice of a given class.
FOR slices:
LIST
OF Slice ¬ scene.entities, slices.rest
UNTIL slices =
NIL
DO
thisType: ATOM ¬ GGSliceOps.GetType[slices.first];
Top level classes
IF (level # leaf
OR GGParent.IsLeafOfClass[slices.first, classType])
AND (classType =
NIL
OR thisType = classType)
THEN {
aborted ¬ walkProc[slices.first];
IF aborted THEN RETURN;
};
Depth first walk
IF (level = all
OR level = leaf
OR (level = highest
AND thisType # classType))
AND GGParent.IsParent[slices.first]
THEN {
aborted ¬ GGParent.WalkChildren[slices.first, level, walkProc, classType];
IF aborted THEN RETURN;
};
ENDLOOP;
};
WalkSelectedSlices:
PUBLIC
PROC [scene: Scene, level: WalkLevel, walkProc: SliceDescriptorWalkProc, selectClass: SelectionClass, classType:
ATOM ¬
NIL]
RETURNS [aborted:
BOOL ¬
FALSE] = {
Built-in classes include: $Cluster, $Outline, $Traj, $Box, $Circle, $Text, and $IP. This routine finds all such slices, even within clusters. class=NIL => all classes. Use level=all to walk every selected slice of a given class.
FOR list:
LIST
OF SliceDescriptor ¬ GGSelect.ListSelected[scene, selectClass], list.rest
UNTIL list =
NIL
DO
thisSlice: Slice ¬ list.first.slice;
thisParts: SliceParts ¬ list.first.parts;
thisType: ATOM ¬ GGSliceOps.GetType[thisSlice];
Top level classes
IF (classType =
NIL
OR thisType = classType)
AND (level # leaf
OR GGParent.IsLeafOfClass[thisSlice, classType])
THEN {
aborted ¬ walkProc[list.first];
IF aborted THEN RETURN;
};
Depth first walk
IF (level = all
OR level = leaf
OR (level = highest
AND thisType # classType))
AND GGParent.IsParent[thisSlice]
THEN {
aborted ¬ GGParent.WalkIncludedChildren[thisSlice, thisParts, level, walkProc, classType];
IF aborted THEN RETURN;
};
ENDLOOP;
};
ListSlices:
PUBLIC
PROC [scene: Scene, level: WalkLevel, classType:
ATOM ¬
NIL]
RETURNS [sliceList:
LIST
OF Slice] = {
ptr: LIST OF Slice;
DoAppendSlice:
PROC [slice: Slice]
RETURNS [done:
BOOL ¬
FALSE] = {
[sliceList, ptr] ¬ GGUtility.AddSlice[slice, sliceList, ptr];
};
[sliceList, ptr] ¬ GGUtility.StartSliceList[];
[] ¬ WalkSlices[scene, level, DoAppendSlice, classType];
};
ListSelectedSlices:
PUBLIC
PROC [scene: Scene, level: WalkLevel, selectClass: SelectionClass, classType:
ATOM ¬
NIL]
RETURNS [selectedList:
LIST
OF SliceDescriptor] = {
ptr: LIST OF SliceDescriptor;
DoAppendSlice:
PROC [childD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
[selectedList, ptr] ¬ GGUtility.AddSliceDescriptor[childD, selectedList, ptr];
};
[selectedList, ptr] ¬ GGUtility.StartSliceDescriptorList[];
[] ¬ WalkSelectedSlices[scene, level, DoAppendSlice, selectClass, classType];
};
CountSlices:
PUBLIC
PROC [scene: Scene, level: WalkLevel, classType:
ATOM ¬
NIL]
RETURNS [count:
INT ¬ 0] = {
AddEmUp:
PROC [child: Slice]
RETURNS [done:
BOOL ¬
FALSE] = {
count ¬ count + 1;
};
[] ¬ WalkSlices[scene, level, AddEmUp, classType];
};
CountSelectedSlices:
PUBLIC
PROC [scene: Scene, level: WalkLevel, selectClass: SelectionClass, classType:
ATOM ¬
NIL]
RETURNS [count:
INT ¬ 0] = {
AddEmUp:
PROC [childD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
count ¬ count + 1;
};
[] ¬ WalkSelectedSlices[scene, level, AddEmUp, selectClass, classType];
};
FirstSelectedSlice:
PUBLIC PROC [scene: Scene, level: WalkLevel, selectClass: SelectionClass, classType:
ATOM ¬
NIL]
RETURNS [sliceD: SliceDescriptor] = {
StopAtFirstSlice:
PROC [childD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
sliceD ¬ childD;
RETURN[TRUE];
};
[] ¬ WalkSelectedSlices[scene, level, StopAtFirstSlice, selectClass, classType];
};
LastSelectedSlice:
PUBLIC PROC [scene: Scene, level: WalkLevel, selectClass: SelectionClass, classType:
ATOM ¬
NIL]
RETURNS [sliceD: SliceDescriptor] = {
StopAtLastSlice:
PROC [childD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
sliceD ¬ childD;
};
[] ¬ WalkSelectedSlices[scene, level, StopAtLastSlice, selectClass, classType];
};
IsTopLevel:
PUBLIC
PROC [slice: Slice]
RETURNS [
BOOL] = {
RETURN[slice.parent = NIL];
};
BoundBoxesInScene:
PUBLIC
PROC [scene: Scene]
RETURNS [bBoxGen: BoundBoxGenerator] = {
Generates all of the boundBoxes in the scene, wherever they occur.
list: LIST OF BoundBox;
thisBox: BoundBox;
FOR entities:
LIST
OF Slice ¬ scene.entities, entities.rest
UNTIL entities =
NIL
DO
IF GGSliceOps.GetType[entities.first]=$Outline
THEN {
-- multiple bound boxes wanted
outlineData: OutlineData ¬ NARROW[entities.first.data];
FOR children:
LIST
OF Slice ¬ outlineData.children, children.rest
UNTIL children=
NIL
DO
thisBox ¬ GGSliceOps.GetBoundBox[children.first, NIL];
list ¬ CONS[thisBox, list];
ENDLOOP;
}
ELSE {
thisBox ¬ GGSliceOps.GetBoundBox[entities.first, NIL];
list ¬ CONS[thisBox, list];
};
ENDLOOP;
bBoxGen ¬
NEW[BoundBoxGeneratorObj ¬ [
list: list
]];
};
TightBoxesInScene:
PUBLIC
PROC [scene: Scene]
RETURNS [bBoxGen: BoundBoxGenerator] = {
Generates all of the boundBoxes in the scene, whereever they occur.
list: LIST OF BoundBox ← ListTightBoxesInScene[scene];
list: LIST OF BoundBox;
thisBox: BoundBox;
FOR entities:
LIST
OF Slice ¬ scene.entities, entities.rest
UNTIL entities =
NIL
DO
thisBox ¬ GGSliceOps.GetTightBox[entities.first, NIL];
list ¬ CONS[thisBox, list];
ENDLOOP;
bBoxGen ¬
NEW[BoundBoxGeneratorObj ¬ [
list: list
]];
};
NextBox:
PUBLIC
PROC [g: BoundBoxGenerator]
RETURNS [next: BoundBox] = {
IF g.list = NIL THEN RETURN[NIL]
ELSE {
next ¬ g.list.first;
g.list ¬ g.list.rest;
};
};
BoundBoxOfScene:
PUBLIC
PROC [scene: Scene]
RETURNS [bBox: BoundBox] = {
box is in local IP coordinates
bbGen: GGModelTypes.BoundBoxGenerator ¬ BoundBoxesInScene[scene];
bBox ¬ GGBoundBox.BoundBoxOfBoxes[bbGen.list]; -- cheating to go inside generator
};
TightBoxOfScene:
PUBLIC
PROC [scene: Scene]
RETURNS [bBox: BoundBox] = {
box is in local IP coordinates
bbGen: GGModelTypes.BoundBoxGenerator ¬ TightBoxesInScene[scene];
bBox ¬ GGBoundBox.BoundBoxOfBoxes[bbGen.list]; -- cheating to go inside generator
};
SelectionBoxOfSelected:
PUBLIC
PROC [scene: Scene, selectClass: SelectionClass ¬ normal, full:
BOOL ¬
FALSE]
RETURNS [bigBox: BoundBox] = {
nextBox: BoundBox;
DoEnlargeBox:
PROC [sliceD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
nextBox ¬
IF GGSliceOps.GetType[sliceD.slice]=$Text
THEN GGSliceOps.GetTightBox[sliceD.slice, IF full THEN NIL ELSE sliceD.parts]
ELSE GGSliceOps.GetBoundBox[sliceD.slice, IF full THEN NIL ELSE sliceD.parts];
GGBoundBox.EnlargeByBox[bBox: bigBox, by: nextBox];
};
bigBox ¬ GGBoundBox.NullBoundBox[];
[] ¬ GGScene.WalkSelectedSlices[scene, first, DoEnlargeBox, selectClass];
};
BoundBoxOfSelected:
PUBLIC
PROC [scene: Scene, selectClass: SelectionClass ¬ normal, sliceLevel:
BOOL ¬
FALSE]
RETURNS [bigBox: BoundBox] = {
nextBox: BoundBox;
DoEnlargeBox:
PROC [sliceD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
nextBox ¬ GGSliceOps.GetBoundBox[sliceD.slice, IF sliceLevel THEN NIL ELSE sliceD.parts];
GGBoundBox.EnlargeByBox[bBox: bigBox, by: nextBox];
};
bigBox ¬ GGBoundBox.NullBoundBox[];
[] ¬ GGScene.WalkSelectedSlices[scene, first, DoEnlargeBox, selectClass];
};
TightBoxOfSelected:
PUBLIC
PROC [scene: Scene, selectClass: SelectionClass ¬ normal, sliceLevel:
BOOL ¬
FALSE]
RETURNS [bigBox: BoundBox] = {
Returns the composite tight box for all selected parts, or all selected slices if sliceLevel=TRUE.
nextBox: BoundBox;
DoEnlargeBox:
PROC [sliceD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
nextBox ¬ GGSliceOps.GetTightBox[sliceD.slice, IF sliceLevel THEN NIL ELSE sliceD.parts];
GGBoundBox.EnlargeByBox[bBox: bigBox, by: nextBox];
};
bigBox ¬ GGBoundBox.NullBoundBox[];
[] ¬ GGScene.WalkSelectedSlices[scene, first, DoEnlargeBox, selectClass];
};
useSelected: BOOL ¬ FALSE;
BoundBoxOfMoving:
PUBLIC
PROC [scene: Scene, editConstraints: GGModelTypes.EditConstraints, bezierDrag: GGInterfaceTypes.BezierDragRecord, selectClass: SelectionClass ¬ normal]
RETURNS [bigBox: BoundBox] = {
routine called to calculate the boundBox of all objects that are about to move; that is, all selected slices, selected CPs, selected joints AND the dangling segments of selected joints AND the segments of selected CPs.
nextBox: BoundBox;
DoEnlargeBox:
PROC [sliceD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
In the common case, drag will be the only non-null descriptor.
overlay, rubber, drag, movingParts: SliceDescriptor;
[----, overlay, rubber, drag] ¬ GGSliceOps.MovingParts[sliceD.slice, sliceD.parts, editConstraints, bezierDrag];
movingParts ¬ GGSliceOps.UnionParts[overlay, rubber];
movingParts ¬ GGSliceOps.UnionParts[movingParts, drag];
nextBox ¬ GGSliceOps.GetBoundBox[sliceD.slice, movingParts.parts];
GGBoundBox.EnlargeByBox[bBox: bigBox, by: nextBox];
};
IF useSelected THEN RETURN[BoundBoxOfSelected[scene, selectClass]];
bigBox ¬ GGBoundBox.NullBoundBox[];
[] ¬ GGScene.WalkSelectedSlices[scene, first, DoEnlargeBox, selectClass];
};
DeleteAllSelected:
PUBLIC
PROC [scene: Scene]
RETURNS [bBox: BoundBox] = {
DeleteTopLevel:
PROC [topLevelD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
IF GGSliceOps.IsCompleteParts[topLevelD]
THEN {
affectedBox ¬ GGSliceOps.GetBoundBox[topLevelD.slice, NIL];
DeleteSlice[scene, topLevelD.slice]; -- fast case
}
ELSE {
newSlices: LIST OF Slice;
priority: INT ¬ GetPriority[scene, topLevelD.slice];
GGSelect.SaveSelectionsInSliceAllClasses[topLevelD.slice, scene];
[newSlices, affectedBox] ¬ DeleteSliceParts[topLevelD];
DeleteSlice[scene, topLevelD.slice];
AddSlices[scene, newSlices, priority];
FOR list:
LIST
OF Slice ¬ newSlices, list.rest
UNTIL list =
NIL
DO
GGSelect.ReselectSliceAllClasses[list.first, scene];
GGSelect.DeselectEntireSlice[list.first, scene, active]; -- do this in case some active selections remain for parts which were not deleted. This fixes a problem when a control point is selected (not a joint) and DEL is invoked but the CP is not deleted.
ENDLOOP;
};
GGBoundBox.EnlargeByBox[bBox, affectedBox];
};
affectedBox: BoundBox;
bBox ¬ GGScene.BoundBoxOfSelected[scene, normal];
GGSelect.DuplicateSelections[scene, normal, active];
[] ¬ GGScene.WalkSelectedSlices[scene, first, DeleteTopLevel, active];
};
DeleteSliceParts:
PROC [sliceD: SliceDescriptor]
RETURNS [newSlices:
LIST
OF Slice, bBox: BoundBox] = {
Returns bounding box of area requiring refresh.
bBox ¬ GGBoundBox.NullBoundBox[];
SELECT GGSliceOps.GetType[sliceD.slice]
FROM
$Traj => ERROR;
$Cluster => {
newChildren: LIST OF Slice;
IF GGSliceOps.IsCompleteParts[sliceD] THEN RETURN[NIL, bBox];
This is a partially-selected cluster. We will return a single cluster, that differs from the original in that DeleteSliceParts has been recursively applied to all of its children. Unfortunately, we have to leave the original Cluster children list structure intact for purposes of Undo, so we will have to carefully construct a new cluster to return.
build a new list of children from old children that are not selected plus modified copies of old children that are selected. Do not change the list structure of the old children in this Cluster.
Walk the first level slices in this cluster, building a new list of children. Call DeleteSliceParts recursively. The only legitimate way to walk the old children and build a new child list is to make child descriptors and walk those recursively.
FOR children:
LIST
OF Slice ← GGParent.ListChildren[sliceD.slice, first], children.rest
UNTIL children=
NIL
DO
thisChild: Slice ← children.first; -- the next child, also possibly a cluster
thisChildD: SliceDescriptor ¬ GGParent.ChildDescriptorFromDescriptor[sliceD, thisChild];
ChildDescriptorFromDescriptor will return NIL for an unselected child
IF thisChildD=
NIL
OR GGSliceOps.IsEmptyParts[thisChildD]
THEN
child is not to be deleted. Put on new list.
newChildren ¬ GGUtility.AppendSliceList[newChildren, LIST[thisChild]]
ELSE {
some piece of child is to be deleted. Recurse.
newBox: BoundBox;
newChildSlices: LIST OF Slice;
[newChildSlices, newBox] ¬ DeleteSliceParts[thisChildD]; -- RECURSIVE CALL
IF newChildSlices#
NIL
THEN {
newChildren ¬ GGUtility.AppendSliceList[newChildren, newChildSlices];
GGBoundBox.EnlargeByBox[bBox: bBox, by: newBox];
};
};
ENDLOOP;
Check for the case that the last child has been removed from the cluster and return NIL if it has.
IF newChildren=NIL THEN RETURN[NIL, bBox]
ELSE {
newCluster: Slice ¬ GGSlice.CreateCluster[frozen: GGSlice.GetFrozen[sliceD.slice]];
GGSlice.AddChildrenToCluster[newCluster, newChildren, -1];
RETURN[LIST[newCluster], bBox];
};
};
$Outline => {
thisBox: BoundBox;
childD: SliceDescriptor;
outlineParts: SliceParts;
original, outline: Slice;
openTrajOutlines: LIST OF Slice;
original ¬ sliceD.slice;
outlineParts ¬ sliceD.parts;
outline ¬ sliceD.slice; -- gets smaller with each iteration
childD ¬ GGParent.FirstIncludedChild[outline, outlineParts, first];
UNTIL childD =
NIL
DO
IF GGSliceOps.GetType[childD.slice] = $Traj
THEN {
IF GGSliceOps.IsCompleteParts[childD]
THEN {
outline ¬ GGOutline.ReplaceChild[outline, childD.slice, NIL];
}
ELSE {
[outline, openTrajOutlines] ¬ GGTraj.DeleteSequence[childD];
newSlices ¬ GGUtility.AppendSliceList[openTrajOutlines, newSlices];
};
}
ELSE {
-- $Box or $Circle
screenStyle: BOOL ¬ TRUE;
fillText: TextNode.Location;
GGBoundBox.EnlargeByBox[bBox: bBox, by: GGSliceOps.GetBoundBox[outline]];
[fillText, screenStyle] ¬ GGSlice.GetBoxText[outline];
outline ¬ GGOutline.ReplaceChild[outline, childD.slice, NIL];
IF outline #
NIL
THEN GGOutline.SetFillText[outline, fillText.node, screenStyle, NIL];
};
IF outline = NIL THEN childD ¬ NIL
ELSE {
outlineParts ¬ GGSliceOps.RemakeSelections[outline, active];
childD ¬ GGParent.FirstIncludedChild[outline, outlineParts, first];
};
ENDLOOP;
IF outline # NIL THEN newSlices ¬ CONS[outline, newSlices]; -- filled outline backmost
thisBox ¬ BoundBoxOfSlices[newSlices];
GGBoundBox.EnlargeByBox[bBox: bBox, by: thisBox];
};
ENDCASE => {
newSlices ¬ NIL;
};
};
BoundBoxOfSlices:
PROC [sliceList:
LIST
OF Slice]
RETURNS [bBox: BoundBox] = {
bBox ¬ GGBoundBox.NullBoundBox[];
FOR list:
LIST
OF Slice ¬ sliceList, list.rest
UNTIL list =
NIL
DO
GGBoundBox.EnlargeByBox[bBox: bBox, by: GGSliceOps.GetBoundBox[list.first, NIL]];
ENDLOOP;
};
SaveSelections:
PUBLIC
PROC [scene: Scene] = {
scene.savedSelected.normal ¬ GGUtility.CopySliceDescriptorList[scene.selected.normal];
scene.savedSelected.featureCycler ¬ scene.selected.featureCycler;
};
RestoreSelections:
PUBLIC
PROC [scene: Scene] = {
GGSelect.DeselectAll[scene, normal]; -- get rid of any transient selections
FOR list:
LIST
OF SliceDescriptor ¬ scene.savedSelected.normal, list.rest
UNTIL list =
NIL
DO
GGSelect.SelectSlice[list.first, scene, normal];
ENDLOOP;
scene.selected.featureCycler ¬ scene.savedSelected.featureCycler;
};
SelectInBox:
PUBLIC
PROC [scene: Scene, box: BoundBox, selectClass: SelectionClass ¬ normal] = {
Select all slices that are completely contained within or on the edge of box.
SelectSlice:
PROC [slice: Slice]
RETURNS [done:
BOOL ¬
FALSE] = {
IF GGSliceOps.WithinBoundBox[slice, box]
THEN GGSelect.SelectEntireSlice[slice, scene, selectClass];
};
[] ¬ GGScene.WalkSlices[scene, first, SelectSlice];
};
SelectPartsInBox:
PUBLIC
PROC [scene: Scene, box: BoundBox, selectClass: SelectionClass ¬ normal] = {
Select all slice parts that are completely contained within or on the surface of box.
inParts: SliceDescriptor;
SelectSliceParts:
PROC [slice: Slice]
RETURNS [done:
BOOL ¬
FALSE] = {
inParts ¬ GGSliceOps.PartsInBoundBox[slice, box];
IF inParts # NIL THEN GGSelect.SelectSlice[inParts, scene, selectClass];
};
[] ¬ GGScene.WalkSlices[scene, first, SelectSliceParts];
};
Getting Exclusive Access to a Scene
<<lockFree: CONDITION;
LockScene:
PUBLIC
ENTRY
PROC [scene: Scene, name:
ATOM, wait:
BOOL ¬
TRUE]
RETURNS [success:
BOOL ¬
FALSE] = {
Name is an indication of what you are going to do while you have locked the scene (e.g., $Get, or $Gravity). Locking is provided to prevent the TIP Notify process from performing spatial calculations on a scene while the Slack process is garbage collecting it.
IF scene.lock = $NotLocked
THEN {
scene.lock ¬ name;
success ¬ TRUE;
}
ELSE {
IF NOT wait THEN RETURN;
UNTIL scene.lock = $NotLocked DO WAIT lockFree ENDLOOP;
scene.lock ¬ name;
success ¬ TRUE;
};
};
UnLockScene:
PUBLIC
ENTRY
PROC [scene: Scene] = {
scene.lock ¬ $NotLocked;
NOTIFY lockFree;
};
>>
END.