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;
};
Priority order
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];
};
Bounding Boxes
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];
};
Selections
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.