diff -r 000000000000 -r dfb7c4ff071f commsfwtools/commstools/svg/relationsVrml.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/commsfwtools/commstools/svg/relationsVrml.pl Thu Dec 17 09:22:25 2009 +0200 @@ -0,0 +1,2243 @@ +# Copyright (c) 1999-2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +# This component and the accompanying materials are made available +# under the terms of "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Nokia Corporation - initial contribution. +# +# Contributors: +# +# Description: +# use strict; +# +# + +use Math::Complex; +use Math::Trig; +require 'getopts.pl'; + +my $version = "0.2 VRML (28/12/07)"; +my $animate = 0; + +# +# Notes for future merging with parseseq.pl: +# - usage of %symtab with "oc" and "od" + +# +# Abstract principles: +# +# Objects are conceptually placed within a variable sized rectangular "super" grid. Each position of this +# grid represents a particular "type" of object (e.g. IP SubConnection Provider). More than one object +# (of the same type) can occupy the same position on this grid. Consequently, each grid position has +# within it a variable sized rectangular "sub-grid" that is used to define the placement of each of the +# individual objects that occupy the same super grid position. The object placement within the sub-grid +# is calculated to avoid connecting lines to neighbouring objects from passing through other objects +# (this is, in fact, the raison d'etre for the sub-grid concept). Note that the sub-grid can have +# larger dimensions than the number of objects within it - i.e. it can have "empty" spaces. This +# is a consequence of the simple placement algorithm: +# +# Each sub-grid in the super grid has a (subRow,subCol) variable that starts with the value (0,0). The +# first object is placed at (0,0). For every subsequent object needs to be placed, if there are has +# any horizontal connections ("west" or "east") to neighbouring objects, then (subRow) variable is +# incremented. if there are any vertical connections, then (subCol) variable is incremented. The object +# is then placed at the final (subRow,subCol) position within the sub-grid, and process repeats. +# +# Each set of inter-related objects forms a "group". The relationship is based around the exchange of +# messages. There may be several such distinct and seperate groups. By definition, no object in one +# group has any message exchange with an object in another group. +# +# Example of two groups: +# +# group 1: +# +# A -- B -- C +# | +# D +# +# group 2: +# +# E -- F -- G +# | +# H +# +# Two groups are merged into one if, whilst reading the message sequence file, a message is found to flow from +# an object in one group is sent to an object in another group. For example, if object D sends a message to +# object E, then a larger group is formed: +# +# +# group 1: +# +# A -- B -- C +# | +# D +# | +# E -- F -- G +# | +# H + + + +# +# Each object is a hash: +# n => array of object references to objects that are "north" of this object +# s => array of object references to objects that are "south" of this object +# w => array of object references to objects that are "west" of this object +# e => array of object references to objects that are "east" of this object +# group => group number. Each group is a set of interconnected objects. +# col => column number of object within super grid +# row => row number of object within super grid +# subCol => column number of object within sub-grid (within supergrid col,row position) +# subRow => row number of objects within sub-grid (within supergrid col,row position) +# +# subGridPlacement[][] is a hash containing the (subRow,subCol) variable pair for a sub-grid at +# a particular and in the overall grid: +# subCol => +# subRow => +# +# colToSubColIndex[] and rowToSubRowIndex[] are arrays containing the cumulative total number of sub-grid +# columns and rows, respectively, at a particular and position in the super grid. +# For example, if the first, second and third columns of the super grid contain sub-grids of +# 2, 1 and 3 columns respectively, then colToSubColIndex[] contains 0, 2, 3, 6, ...). +# +# colToSubGridWidth[] and rowToSubGridHeight[] are arrays containing the width and height respectively +# of the sub-grid at a particular and position in the super grid, respectively. +# +# gridVerticalLineCount[] and gridHorizontalLineCount[] are arrays containing the number +# of "long" lines that run vertically and horizontally at certain and positions in the +# grid for connecting objects that are not neighbours. Objects connected to immediate neighbours +# do not have "long" lines and are not counted here. +# +# groupRanges{} is a hash which contains +# minCol => minimum column number of any object within the group +# maxCol => maximum column number of any object within the group +# minRow => minimum row number of any object within the group +# maxRow => maximum row number of any object within the group +# row => row number that the group starts at within the super grid +# + +# VRML line array elements +use constant X => 0; +use constant Y => 1; +use constant Z => 2; +# fonts +use constant FONT_FAMILY => "SANS"; +use constant Z_POSITION => 0; + +# +# Flags +my $displayObjectAddressFlag = 0; # display object hex address alongside name + +my $group = 1; # next free group number (monotonically incremented after allocation) +my $groupVerticalSeperation = 3; + +my %directionTranslate = ( n => { x => 0, y => -1 }, + s => { x => 0, y => +1 }, + w => { x => -1, y => 0 }, + e => { x => +1, y => 0 } ); + +my %initRange = ( minCol => 9999, minRow => 9999, maxCol => -9999, maxRow => -9999 ); +my %overallRange = %initRange; + +# tabLabel is a monotonically increasing value used to derive a unique Tab Label. It is incremented +# everytime a new Tab is drawn. + +my $tabLabel = "a"; + +my @colToSubColIndex; +my @rowToSubRowIndex; +my @rowToSubGridHeight; +my @colToSubGridWidth; +my %relationships; +my %groupRanges; +my @objects; +my @subGridPlacement; + +Getopts("x:vV"); + +readRelationshipFile(); + +print "Reading sequences\n" if ($opt_V); +readSequences(); +#printGroupRanges(); + +#printRelationships(); +print "Normalising coordinates\n" if ($opt_V); +normaliseCoords(); +print "Seperating groups\n" if ($opt_V); +seperateGroups(); +print "Seperating overlaps\n" if ($opt_V); +seperateOverlaps(); + +# print2DArray("subGridPlacement", \@subGridPlacement); + +print "Calculating grid positions\n" if ($opt_V); +calculateGridPositions(); + +if ($opt_v) { + printArray("colToSubColIndex", \@colToSubColIndex); + printArray("rowToSubRowIndex", \@rowToSubRowIndex); + + printArray("rowToSubGridHeight", \@rowToSubGridHeight); + printArray("colToSubGridWidth", \@colToSubGridWidth); + + printLoners(); + printRelationships(); + printGroupRanges(); +} + +#my $outputFormat = "svg"; +my $outputFormat = "vrml"; +my $adjustFactor; +if ($outputFormat eq "vrml") { + $adjustFactor = 0.1; +} else { + $adjustFactor = 1; +} + +#VRML specific +my $vrmlCycleInterval = 0.2; +my $boxDepth = 20 * $adjustFactor; +my $lineThickness = 2 * $adjustFactor; +my $lineExtensionFactor = 1.01; # *** REMOVE THIS *** +my $initialTransparency = $animate ? 1 : 0; +my $boxDepthMargin = 120 * $adjustFactor; +my $boxDepthWithMargin = $boxDepth + $boxDepthMargin; +my $boxInternalTextColour = colourString(0,100,100); + +my $tabSpacing = 10 * $adjustFactor; +# minor aesthetic adjustments to the spacing between the end of a Tab line and its Labels +my $tabToFontAdjustTopY = 2 * $adjustFactor; +my $tabToFontAdjustBottomY = 1 * $adjustFactor; +my $tabLineLengthY = 5 * $adjustFactor; + +my $fontHeight = 9 * $adjustFactor; # assumed font height +my $boxInternalFontHeight = $fontHeight / 3; +my $groupHighlightFill = colourString(235,235,235); +my $groupHighlightMargin = 5 * $adjustFactor; # margin of the group background rectangle + +my $horizMargin = 20 * $adjustFactor; +my $rectangleMargin = 15 * $adjustFactor; + +#Sphere +#my $rectangleWidth = 50 * $adjustFactor; +#my $rectangleHeight = 50 * $adjustFactor; +my $rectangleWidth = 110 * $adjustFactor; +my $rectangleHeight = 27 * $adjustFactor; + +my $rectangleWidthWithMargin = $rectangleWidth + $rectangleMargin; +my $rectangleHeightWithMargin = $rectangleHeight + $rectangleMargin; +my $vertMargin = $horizMargin; + +my $gridXSpacing = $horizMargin; +my $gridYSpacing = $gridXSpacing; + +my $rectangleOpacity = 0.80; +my $rectangleColour1 = colourString(144,238,144); # "lightgreen" +my $rectangleColour2 = colourString(135,206,250); # "lightskyblue" +my $rectHighlightColour = colourString(250,128,114); # "salmon" + +# X position of last grid column + size of last grid column + horizontal margin +my $width = gridX($#colToSubColIndex) + $colToSubGridWidth[$#colToSubColIndex] * $rectangleWidthWithMargin + $horizMargin; +my $height = gridY($#rowToSubRowIndex) + $rowToSubGridHeight[$#subGridRow] * $rectangleHeightWithMargin + $vertMargin; + +createOutputFile(); +outputDocHeader($width,$height); +# Not for VRML (yet) +#outputGroupHighlight(); +print "Generating links\n" if ($opt_V); +outputLinks(); +print "Generating nodes\n" if ($opt_V); +outputNodes(); +outputDocFooter(); +if ($animate) { + print "Generating animation\n" if ($opt_V); + AnimateDo(); +} +closeOutputFile(); +if ($outputFormat eq "svg") + { + outputRelationsHTMLEmbedder($width, $height); + } + +sub colourString($$$) + { + my ($r,$g,$b) = @_; + if ($outputFormat eq "svg") { + return "rgb($r,$g,$b)"; + } + else { + my $str = sprintf("%.3f %.3f %.3f", $r/255, $g/255, $b/255); + return $str; + } + } + +sub createOutputFile() + { + if ($outputFormat eq "svg") + { + open SVG, ">relations.svg" || die "Cannot open relations.svg for writing\n"; + } + else + { + open WRL, ">relations.wrl" || die "Cannot open relations.wrl for writing\n"; + } + } + +sub closeOutputFile() + { + if ($outputFormat eq "svg") + { + close SVG; + } + else + { + close WRL; + } + } + +sub readSequences() + { + while (<>) { + die unless s/^(\w+)\s+//; + my $action = $1; + if ($action eq "p" || $action eq "r" || $action eq "sc") + { + # Post/Receive + # [P|R] + my $msg; + if (/^"/) + { + s/^"([^"]+)"\s+//; + $msg = $1; + } + else + { + s/^(\S+)\s+//; + $msg = $1; + } + $msg =~ s/ \(.*\)//; # remove message arguments in parentheses: "()" => "" + split; + my $srcRef = findOrCreateObject(shift @_); + my $destRef = findOrCreateObject(shift @_); + my $direction = $relationships{$msg}; + if ($direction) { + if (isDirection($direction)) { + AddRelation($srcRef, $destRef, $direction); + } + } + elsif ($srcRef != $destRef) { +# print "Unknown relationship: $srcRef->{Name} -- $msg --> $destRef->{Name}\n"; + } + AnimateObjectMessage($srcRef, $destRef, $direction) if $animate; + } + elsif ($action eq "oc") + { + # Object Create + # oc + split; + my $objRef = findOrCreateObject(shift @_); + shift @_; # order - can we use this to define object column placement ? + $objRef->{Addr} = shift @_; + AnimateObjectCreate($objRef) if $animate; + } + elsif ($action eq "od") + { + # Object Destroy + # od + split; + my $objRef = findObject(shift @_); + if ($objRef != 0) { + AnimateObjectDestroy($objRef) if $animate; + } + } + } + } + +sub AddRelation($$$) + { + my ($srcRef,$destRef,$dir) = @_; + + my $rdir = reverseDirection($dir); + + if ($srcRef != $destRef && ! RelationExists($srcRef->{$dir}, $destRef)) + { + push @{$srcRef->{$dir}}, $destRef; + push @{$destRef->{$rdir}}, $srcRef; + + # my %range = %initRange; + + # we are about to merge groups, so delete the group about to be assimilated from the group ranges hash + delete $groupRanges{$destRef->{group}}; + + my $rangeRef = \%{$groupRanges{$srcRef->{group}}}; + + MergeGroups($destRef->{group}, $srcRef->{group}, + $srcRef->{col} + $directionTranslate{$dir}{x} - $destRef->{col}, + $srcRef->{row} + $directionTranslate{$dir}{y} - $destRef->{row}, $rangeRef ); + + # MergeGroups() above will automatically adjust the min/max range values in range for all the "incoming" + # objects. However, we still have to take into account the effect that the original src object has on + # the min/max range values, so call updateRange() specially here for it. + updateRange($rangeRef, $srcRef->{col}, $srcRef->{row}); + + # Ultimately we need to place objects on the super grid according to what type they are - for example, + # flows in col 0, SCPRs in col 1 etc. At the moment, they are mixed up in the same super grid, as + # some groups may have flow, some may not, but all groups are left aligned. This explains why + # the display is messed up with MCPRs and CPRs occupying the same column in different groups. + + updateRange(\%overallRange, $rangeRef->{minCol}, $rangeRef->{minRow}); + updateRange(\%overallRange, $rangeRef->{maxCol}, $rangeRef->{maxRow}); + } + } + +# +# Indicate whether the string passed is a (compass) direction. +# +sub isDirection($) +{ + my $dir = $_[0]; + return $dir eq "n" || $dir eq "s" || $dir eq "w" || $dir eq "e"; +} + +sub MergeGroups($$$$) + { + my ($targetGroup, $newGroup, $adjustCol, $adjustRow, $rangeRef) = @_; + + foreach my $obj (@objects) { + if ($obj->{group} == $targetGroup) { +# print "Merge $obj->{Name}, group $obj->{group} => $newGroup, ($obj->{col},$obj->{row}) => "; + $obj->{group} = $newGroup; + $obj->{col} += $adjustCol; + $obj->{row} += $adjustRow; +# print "($obj->{col},$obj->{row})\n"; + # determine minimum and maximum row,col extent of group + updateRange($rangeRef, $obj->{col}, $obj->{row}); + } + } + } + +sub updateRange($range, $col, $row) + { + my ($range, $col, $row) = @_; + + if (!defined($range->{maxCol}) || $col > $range->{maxCol}) { + $range->{maxCol} = $col; + } + if (!defined($range->{maxRow}) || $row > $range->{maxRow}) { + $range->{maxRow} = $row; + } + if (!defined($range->{minCol}) || $col < $range->{minCol}) { + $range->{minCol} = $col; + } + if (!defined($range->{minRow}) || $row < $range->{minRow}) { + $range->{minRow} = $row; + } + } + +sub RelationExists($$) + { + my ($relationArrayRef, $objRef) = @_; + + if (defined ($relationArrayRef)) + { + foreach my $obj (@{$relationArrayRef}) + { + if ($obj == $objRef) + { + return 1; + } + } + } + else + { + return 0; + } + } + +sub reverseDirection($) + { + my ($dir) = @_; + my %reverseMap = ( "n" => "s", "s" => "n", "w" => "e", "e" => "w" ); + return $reverseMap{$dir}; + } + +# +# Print objects that exchange no messages with anyone else. +# +sub printLoners() + { + my $wrapCount = 8; + my $wrapIndex = 1; + print "Loners:\t"; + my $commaRequired = 0; + for (my $i = 0 ; $i <= $#objects ; ++$i) + { + my $obj = $objects[$i]; + if (!defined($obj->{subCol}) && !defined($obj->{subRow})) + { + if ($commaRequired != 0) + { + print ", "; + } + $commaRequired = 1; + print $obj->{Name}; + if (($wrapIndex++ % $wrapCount) == 0) + { + $commaRequired = 0; + print "\n\t"; + } + } + } + print "\n\n"; + } + +sub printRelationships() + { + print "Relationships - overallRange: ($overallRange{minCol}, $overallRange{minRow}) to ($overallRange{maxCol}, $overallRange{maxRow}):\n"; + my $i; + for ($i = 0 ; $i < scalar(@objects) ; ++$i) + { + printObjectRelationships($objects[$i]); + } + print "\n"; + } + +sub printObjectRelationships($) + { + my ($obj) = @_; + + # Object + print $obj->{Name}, ": group $obj->{group}, ($obj->{col},$obj->{row}), sub ($obj->{subCol},$obj->{subRow})\n"; + + # North + if (defined($obj->{n})) + { + print "\tN: "; + printObjects($obj->{n}); + print "\n"; + } + + # West + if (defined($obj->{w})) + { + print "\tW: "; + printObjects($obj->{w}); + print "\n"; + } + + + # East + if (defined($obj->{e})) + { + print "\tE: "; + printObjects($obj->{e}); + print "\n"; + } + + # South + if (defined($obj->{s})) + { + print "\tS: "; + printObjects($obj->{s}); + print "\n"; + } + + print "\n"; + } + +sub printObjects($) + { + my ($relationArray) = @_; + + my $commaRequired = 0; + foreach my $obj (@{$relationArray}) + { + if ($commaRequired == 1) + { + print ", "; + } + print $obj->{Name}; + $commaRequired = 1; + } + } + +sub findObject($) + { + my $objName = $_[0]; + my $i; + for ($i = 0 ; $i < scalar(@objects) ; ++$i) { + if ($objects[$i]{Name} eq $objName) { + return $objects[$i]; + } + } + return 0; + } + +sub findOrCreateObject($) + { + my $objName = $_[0]; + my $objRef = findObject($objName); + if ($objRef != 0) { + return $objRef; + } + else { + my $objectCount = scalar(@objects); + $objects[$objectCount] = { Name => $objName, row => 0, col => 0, group => $group++ }; +# print "findOrCreateObject: $objects[$i]->{Name}, group $objects[$i]->{group}, ($objects[$i]->{col},$objects[$i]->{row})\n"; + return $objects[$objectCount]; + } + } + +sub readRelationshipFile() + { + if ($opt_x) { + open (REL, $opt_x) || die "Cannot open exclude file $opt_x\n"; + while () { + chomp; + # remove leading and trailing blanks and ignore blank lines + s/^\s+//; + s/\s+$//; + if (! $_) + { next; }; + + my ($message,$direction) = split; + $relationships{$message} = $direction; + } + close REL; + } + } + +####################### +# SVG output routines +####################### + +sub outputDocHeader() + { + if ($outputFormat eq "svg") + { + outputSVGDocHeader(); + } + else + { + outputVRMLDocHeader(); + } + } + +sub outputSVGDocHeader() + { + my ($width,$height) = @_; + print SVG '',"\n"; + print SVG '',"\n"; + print SVG "\n"; + # place popup rectangle before all other text, otherwise it will be on top of rather than + # underneath the popup text + } + +sub outputText($$$$$$$) + { + if ($outputFormat eq "svg") + { + outputSVGText(@_); + } + else + { + outputVRMLText(@_); + } + } + +# Not working +sub outputTextAlongLine($$$$$$$) + { + if ($outputFormat eq "svg") { + return; + } + my ($x1,$y1,$z1,$x2,$y2,$z2,$text) = @_; + + my $rotation = ""; + { + my $rotateRef = calcAxialRotationAndLength([$x1, -$y1, $z1] , [$x2, -$y2, $z2]); + printHash("Text Rotation", $rotateRef); + print "\n"; + if (exists $rotateRef->{angle}) + { + $rotation = "vrml: rotation $rotateRef->{x} $rotateRef->{y} $rotateRef->{z} " . ($rotateRef->{angle} - (pi/2)); + } + } + outputText(($x1 + $x2) / 2, $text, ($y1 + $y2) / 2, ($z1 + $z2) / 2, "middle", $rotation, ""); + } + + +sub outputSVGText($$$$$$$) + { + my ($x,$text,$y,$z,$anchor,$otherSvgAttr,$colour) = @_; + + my $attrs = qq{ x="$x" y="$y" }; + + if ($otherSvgAttr) + { + $attrs .= qq { $otherSvgAttr }; + } + + if ($anchor) + { + $attrs .= qq { text-anchor="$anchor" }; + } + + if ($colour) + { + if ($colour =~ s/^!//) + { + outputRect($x - 1, $y - $fontHeight, $z, 3, $fontHeight, "white", "white", ""); + } + if ($colour) + { + $attrs .= qq { fill="$colour" }; + } + } + + print SVG "$text\n"; + } + +sub outputLine($$$$$$$$) + { + if ($outputFormat eq "svg") + { + outputSVGLine(@_); + } + else + { + outputVRMLLine(@_); + } + } + +sub outputSVGLine($$$$$$$$) + { + my ($x1,$y1,$z1,$x2,$y2,$z2,$colour,$otherAttr) = @_; + + if (! $colour) { + $colour = "black"; + } + + print SVG qq{\n}; + } + +sub outputVerticalLine() + { + if ($outputFormat eq "svg") + { + outputSVGVerticalLine(@_); + } + else + { + outputVRMLVerticalLine(@_); + } + } + +sub outputSVGVerticalLine() + { + my ($x,$y1,$y2,$colour) = @_; + if (! $colour) { $colour = "black"; } + print SVG qq{\n}; + } + +sub outputRect($$$$$$$$) + { + if ($outputFormat eq "svg") + { + outputSVGRect(@_); + } + else + { + outputVRMLRect(@_); + } + } + +sub outputSVGRect($$$$$$$) + { + my ($x,$y,$z,$width,$height,$fill,$stroke,$otherAttr) = @_; + print SVG qq {\n}; + } + +sub outputDocFooter() + { + if ($outputFormat eq "svg") + { + outputSVGDocFooter(); + } + } + +sub outputSVGDocFooter() + { +################################### +# Begin Interpolated Text # +################################### + print SVG <<'EOT' + + +EOT +################################# +# End Interpolated Text # +################################# + } + +sub outputRelationsHTMLEmbedder($$) + { + my ($width,$height) = @_; + open HTML, ">relations.html" || die "Cannot open relations.html for writing\n"; + print HTML qq{\n\n}; + print HTML qq{\n}; + print HTML qq{ + +EOT +; + close HTML; + } + + +############################################### +# +# VRML Routines +# +############################################### + +sub outputVRMLDocHeader() + { + ########################### + # Begin Interpolated Text # + ########################### + print WRL<[X] . $sep . $coordP->[Y] . $sep . $coordP->[Z]; +} + +sub outputVRMLText($$$$$$$) + { + my ($x,$text,$y,$z,$anchor,$otherAttr,$colour) = @_; +# print "outputVRMLText(x=$x, text=$text, y=$y, z=$z, anchor=$anchor, attr=$otherAttr, colour=$colour)\n"; + my $coordRef = [ $x, -$y, $z ]; + + if ($colour !~ /^\d/) { + $colour = "1 1 1"; + } + my $rotation = ""; + my $def = ""; + my $size = $fontHeight; # default + if (hasVrmlTokens(\$otherAttr) == 1) { + my $s = ""; + while ($s = getTokens(\$otherAttr)) { + if ($s =~ /^rotation /) { + $rotation = $s; + } elsif ($s =~ /^size\s*(.*)/) { + $size = $1; + } elsif ($s =~ /DEF\s*(\S+)/) { + $def = $1; + } + } + } + print WRL "Transform {\n"; + if ($rotation ne "") { + print WRL " $rotation\n"; + } + print WRL " translation ", formatVrmlCoord($coordRef, " "), "\n"; + print WRL " children Shape {\n"; + print WRL " appearance Appearance {\n"; + if ($def) { + print WRL " material DEF $def Material {\n"; + } else { + print WRL " material Material {\n"; + } + print WRL " diffuseColor $colour\n"; + print WRL " emissiveColor $colour\n"; + print WRL " transparency $initialTransparency\n"; + print WRL " }\n"; + print WRL " }\n"; + print WRL " geometry Text {\n"; + print WRL " string \"$text\"\n"; + print WRL " fontStyle FontStyle {\n"; + print WRL " size $size\n"; + print WRL qq{ justify "MIDDLE"\n }; + print WRL " family \"", FONT_FAMILY, "\"\n"; +# print WRL qq{ style "BOLD"\n }; + print WRL " }\n"; + print WRL " }\n"; + print WRL " }\n"; + print WRL "}\n"; + + } + +sub outputVRMLLine($$$$$$$$) + { + my ($x1,$y1,$z1,$x2,$y2,$z2,$colour,$otherAttr) = @_; +# print "outputVRMLLine(x1=$x1, y1=$y1, z1=$z1, x2=$x2, y2=$y2, z2=$z2, colour=$colour, attr=$otherAttr)\n"; + + my $def; + if (hasVrmlTokens(\$otherAttr) == 1) { + my $s = ""; + while ($s = getTokens(\$otherAttr)) { + if ($s =~ /^DEF\s*(\S+)/) { + $def = $1; + } + } + } + + + my $p1Ref = [ $x1, -$y1, $z1 ]; + my $p2Ref = [ $x2, -$y2, $z2 ]; + $colour = "1 1 1"; + my $rotateRef = calcAxialRotationAndLength($p1Ref, $p2Ref); +# printHash("Rotation", $rotateRef); +# print "\n"; + my @lineCentre = (($p1Ref->[X] + $p2Ref->[X]) / 2, ($p1Ref->[Y] + $p2Ref->[Y]) / 2, ($p1Ref->[Z] + $p2Ref->[Z]) / 2); + print WRL "Transform {\n"; + if (exists $rotateRef->{angle} && $rotateRef->{angle} != 0) + { + print WRL " rotation $rotateRef->{x} $rotateRef->{y} $rotateRef->{z} $rotateRef->{angle}\n"; + } + print WRL " translation @lineCentre\n"; + print WRL " children Shape {\n"; + print WRL " appearance Appearance {\n"; + if ($def) { + print WRL " material DEF $def Material {\n"; + } else { + print WRL " material Material {\n"; + } + print WRL " diffuseColor $colour\n"; +# print WRL " emissiveColor 1 0 0\n"; + print WRL " transparency $initialTransparency\n"; + print WRL " }\n"; + print WRL " }\n"; + print WRL " geometry Cylinder {\n"; + print WRL " bottom FALSE\n"; + print WRL " top FALSE\n"; + print WRL " radius $lineThickness\n"; + print WRL " height $rotateRef->{length3D}\n"; + print WRL " }\n"; + print WRL " }\n"; + print WRL "}\n"; + } + +sub outputVRMLVerticalLine() + { + my ($x,$y1,$y2,$colour) = @_; +# print "outputVRMLVerticalLine(x=$x, y1=$y1, y2=$y2, colour=$colour)\n"; + outputVRMLLine($x, $y1, 0, $x, $y2, 0, $colour, ""); + } + +sub outputVRMLRect($$$$$$$$) + { + my ($x,$y,$z,$width,$height,$fill,$stroke,$otherAttr) = @_; +# print "outputVRMLRect(x=$x, y=$y, z=$z, width=$width, height=$height, fill=$fill, stroke=$stroke, attr=$otherAttr)\n"; + + my $def; + if (hasVrmlTokens(\$otherAttr) == 1) { + my $s = ""; + while ($s = getTokens(\$otherAttr)) { + if ($s =~ /^DEF\s*(\S+)/) { + $def = $1; + } + } + } + + my $posRef = [ $x + ($width/2), -($y + $height/2), $z ]; + my $sizeRef = [ $width, $height, $boxDepth ]; + print WRL "Transform {\n"; + print WRL " translation @{$posRef}\n"; + print WRL " children Shape {\n"; + print WRL " appearance Appearance {\n"; + if ($def) { + print WRL " material DEF $def Material {\n"; + } else { + print WRL " material Material {\n"; + } + print WRL " diffuseColor $fill\n"; +# print WRL " emissiveColor $fill\n"; + print WRL " transparency $initialTransparency\n"; + print WRL " }\n"; + print WRL " }\n"; + print WRL " geometry Box {\n"; + print WRL " size @{$sizeRef}\n"; +#Sphere +# print WRL " geometry Sphere {\n"; +# print WRL " radius ", $width/2, "\n"; + print WRL " }\n"; + print WRL " }\n"; + print WRL "}\n"; + } + +sub calcAxialRotationAndLength($$) + { + my ($p1, $p2) = @_; # references + if ($p1->[Y] >= $p2->[Y]) { + my $temp = $p1; + $p1 = $p2; + $p2 = $temp; + } + my $x = ($p2->[X] - $p1->[X]); + my $y = ($p2->[Y] - $p1->[Y]); + my $z = ($p2->[Z] - $p1->[Z]); + my $lengthXYZ = sqrt($x*$x + $y*$y + $z*$z); + my $rotationAngle = asin($y / $lengthXYZ); + $rotationAngle = (pi/2) - $rotationAngle; + # degenerate cases + $lengthXYZ *= $lineExtensionFactor; + if ($z == 0) + { + return { x => 0, y => 0, z => -1, angle => $rotationAngle, length3D => $lengthXYZ }; + } + elsif ($x == 0) + { + if ($z < 0) { + $rotationAngle = -$rotationAngle; + } + return { x => 1, y => 0, z => 0, angle => $rotationAngle, length3D => $lengthXYZ }; + } + my $perpendicularGradientXZ = -1 / ($z / $x); + if ($p2->[X] > $p1->[X]) { + if ($perpendicularGradientXZ > 0) { + $rotationAngle = -$rotationAngle; + } + } + else { + if ($perpendicularGradientXZ < 0) { + $rotationAngle = -$rotationAngle; + } + } + return { x => 1, y => 0, z => $perpendicularGradientXZ, angle => $rotationAngle, length3D => $lengthXYZ }; + } + +################ +# +# +# +################ + +sub outputNodes() +{ + my $obj; + foreach $obj (@objects) { + if (hasRelationships($obj)) { + # + # figure out the colours to use, based on a chequer board pattern + # + my $colour; + if ($obj->{col} & 1) { + $colour = $rectangleColour1; + } else { + $colour = $rectangleColour2; + } + my $x = objectX($obj); + my $y = objectY($obj); + my $z = objectZ($obj); + if ($outputFormat eq "svg") { + outputRect($x, $y, $z, + $rectangleWidth, $rectangleHeight, + $colour, "black", qq{ onclick="NodeEvent(evt)" opacity="$rectangleOpacity" id="$obj->{Name}" } ); + } else { + outputRect($x, $y, $z, + $rectangleWidth, $rectangleHeight, + $colour, "black", "vrml: DEF $obj->{Name}"); + } + + my $debug; +# $debug="-" . $obj->{group} . "(" . $obj->{col} . "," . $obj->{row} . ")(" . $obj->{subCol} . "," . $obj->{subRow} . ")"; + my $midPointX = $x + ($rectangleWidth/2); + + my $spacingY = ($rectangleHeight + $fontHeight); + if ($displayObjectAddressFlag) { + $spacingY /= 3; + } else { + $spacingY /= 2; + } + my $textZOffset = $boxDepth/2 + $adjustFactor; + + my $otherAttr; + if ($outputFormat ne "svg") { + $otherAttr = "vrml: DEF $obj->{Name}_TF"; + } else { + $otherAttr = qq{id="$obj->{Name}" onclick="NodeEvent(evt)"}; + } + + outputText($midPointX, + $obj->{Name} . $debug, + $y + $spacingY, + $z + $textZOffset, + "middle", $otherAttr, "black"); + + if ($outputFormat ne "svg") { + # Object name at rear side of box + outputText($midPointX, + $obj->{Name} . $debug, + $y + $spacingY, + $z - $textZOffset, + "middle", "vrml: rotation 0 1 0 " . pi . " ; DEF $obj->{Name}_TB", "black"); + } + + if ($displayObjectAddressFlag) { + if ($outputFormat ne "svg") { + outputText($midPointX, $obj->{Addr}, $y + $spacingY, $z, "middle", "vrml: size " . $boxInternalFontHeight, $boxInternalTextColour); + } else { + outputText($midPointX, + $obj->{Addr}, + $y + 2*$spacingY, + $z + $textZOffset, + "middle", "", "black"); + } + } + } + } +} + +sub outputLinks() + { + my $obj; + foreach $obj (@objects) { + # relationships between objects are symmetrical in both directions, so only need to display + # one each of west/east and north/south. + if ($obj->{n}) { + foreach my $obj2 (@{$obj->{n}}) { + if (checkOverlap($obj, $obj2) == 1) + { +# ++$gridVerticalLineCount[$obj->{col}]; + my $attachPoint2 = ++$obj2->{n_AttachPointCount}; + my $attachPoint = ++$obj->{n_AttachPointCount}; + outputTabConnectors($obj, "n", $attachPoint, $obj2, "s", $attachPoint2); + } + else + { + my $x1 = objectSideX($obj, "n", 0); + my $y1 = objectSideY($obj, "n", 0); + my $z1 = objectZ($obj); + my $x2 = objectSideX($obj2, "s", 0); + my $y2 = objectSideY($obj2, "s", 0); + my $z2 = objectZ($obj2); + my $otherAttr; + if ($outputFormat eq "svg") { + $otherAttr = qq{ id="$obj->{Name}_$obj2->{Name}" onclick="MessageEvent(evt)" }; + } else { + $otherAttr = "vrml: DEF " . vrmlRelationshipName($obj->{Name}, $obj2->{Name}); + } + outputLine($x1, $y1, $z1, $x2, $y2, $z2, "", $otherAttr); + +# outputTextAlongLine($x1,$y1,$z1,$x2,$y2,$z2,"message"); + } + } + } + if ($obj->{w}) { + foreach my $obj2 (@{$obj->{w}}) { + if (checkOverlap($obj, $obj2) == 1) + { +# ++$gridHorizontalLineCount[$obj->{row}]; + my $attachPoint2 = ++$obj2->{w_AttachPointCount}; + my $attachPoint = ++$obj->{w_AttachPointCount}; + outputTabConnectors($obj, "w", $attachPoint, $obj2, "e", $attachPoint2); + } + else + { + my $x1 = objectSideX($obj, "w", 0); + my $y1 = objectSideY($obj, "w", 0); + my $z1 = objectZ($obj); + my $x2 = objectSideX($obj2, "e", 0); + my $y2 = objectSideY($obj2, "e", 0); + my $z2 = objectZ($obj2); + + if ($outputFormat eq "svg") { + $otherAttr = qq{ id="$obj->{Name}_$obj2->{Name}" onclick="MessageEvent(evt)" }; + } else { + $otherAttr = "vrml: DEF " . vrmlRelationshipName($obj->{Name}, $obj2->{Name}); + } + outputLine($x1, $y1, $z1, $x2, $y2, $z2, "", $otherAttr); +# outputTextAlongLine($x1,$y1,$z1,$x2,$y2,$z2,"message"); + } + } + } + } + } + + +# +# Output a unique background rectangle around each group in order to highlight each group seperately +# +sub outputGroupHighlight() + { + my $x = gridX(0) - $groupHighlightMargin; + for my $i (keys %groupRanges) + { + my $ref = $groupRanges{$i}; + die "outputGroupHighlight(): minRow for group $i is non-zero ($ref->{minRow})\n" if $ref->{minRow} != 0; + outputRect($x, + gridY($ref->{row}) - $groupHighlightMargin, + 0, + $width, + gridHeight($ref->{row}, $ref->{maxRow} + 1) + $groupHighlightMargin * 2, +# $groupHighlightFill, "", qq { onclick="debugEvent(evt)" } ); + $groupHighlightFill, "", ""); + } + } + +# +# Check if a line drawn between two objects will cross through another object +# + +sub checkOverlap($$) + { + my ($obj, $obj2) = @_; + if (abs($obj->{col} - $obj2->{col}) > 1 || abs($obj->{row} - $obj2->{row}) > 1) + { + return 1; + print "Overlap $obj->{Name} and $obj2->{Name}\n"; + } + return 0; + } + +# +# Output labelled tabs representing the connection between two nodes which have intervening nodes (so +# a direct line is not possible) +# + +sub outputTabConnectors($$$$$$) + { + my ($obj, $dir, $attachPoint, $obj2, $dir2, $attachPoint2) = @_; + + my $objX = objectSideX($obj, $dir, $attachPoint); + my $objY = objectSideY($obj, $dir, $attachPoint); + my $objZ = objectZ($obj); + + my $obj2X = objectSideX($obj2, $dir2, $attachPoint2); + my $obj2Y = objectSideY($obj2, $dir2, $attachPoint2); + my $obj2Z = objectZ($obj2); + + if ($dir eq "n") + { + # output Tabs at the top of objects - de-emphasise the tab labels with semi-condensed font option + my $otherAttrs = ""; + if ($outputFormat ne "svg") { + $otherAttrs = "vrml: DEF " . vrmlRelationshipName($obj->{Name}, $obj2->{Name}); + } + outputLine($objX, $objY, $objZ, $objX, $objY - $tabLineLengthY, $objZ, "", $otherAttrs); + outputText($objX, getCurrentTabLabel(), + $objY - $tabLineLengthY - $tabToFontAdjustTopY, + $objZ, + "middle", qq{font-stretch="semi-condensed"}, ""); + + # output Tabs at the bottom of objects + outputLine($obj2X, $obj2Y + $tabLineLengthY, $obj2Z, $obj2X, $obj2Y, $obj2Z, "", ""); + outputText($obj2X, getCurrentTabLabel(), $obj2Y + $tabLineLengthY + $fontHeight + $tabToFontAdjustBottomY, + $obj2Z, + "middle", qq{font-stretch="semi-condensed"}, ""); + + incrementCurrentTabLabel(); + } + elsif ($dir eq "w") + { + } + else + { + die "outputTabConnectors: incorrect direction $dir\n"; + } + } + +# +# Return the current Tab Label string +# +sub getCurrentTabLabel() + { + return $tabLabel; + } + +# +# Increment the current Tab Label. +# +# Cycle through a-z, A-Z, 0-9 (...then funny characters I guess!) +# +sub incrementCurrentTabLabel() + { + if ($tabLabel eq "z") + { + $tabLabel = "A"; + } + elsif ($tabLabel eq "Z") + { + $tabLabel = "0"; + } + else + { + $tabLabel++; + } + } + +# +# Return the X position of a particular column in the super grid +# + +sub gridX($) + { + my ($col) = @_; + return $horizMargin + $colToSubColIndex[$col] * ($rectangleWidthWithMargin) + ($col * $gridXSpacing); + } + +# +# Return the Y position of a particular row in the super grid +# + +sub gridY($) + { + my ($row) = @_; + return $vertMargin + $rowToSubRowIndex[$row] * $rectangleHeightWithMargin + ($row * $gridYSpacing); + } + +# +# Return the height (pixels) of an area starting at a specified row and a number of rows high. +# +sub gridHeight($$) + { + my ($startRow,$heightInRows) = @_; + my $endRow = $startRow + $heightInRows - 1; + die "gridHeight: invalid row height $heightInRows\n" if $heightInRows <= 0; + + # calculate the number of sub rows within the area concerned + my $heightInSubRows = $rowToSubRowIndex[$endRow] - $rowToSubRowIndex[$startRow] + $rowToSubGridHeight[$endRow]; + + # height is: height of rectangles + height of spacing between rectangles + height of spacing between rows + # The "-1"s are to exclude the spacing after the last row. + return ($heightInSubRows * $rectangleHeight) + + (($heightInSubRows - 1) * $rectangleMargin) + + (($heightInRows - 1) * $gridYSpacing); + } + + +# +# Return the X position of one of the object's sides +# +# Arguments: +# obj object in question +# dir the side in question expressed as a compass point - i.e. "n", "s", "w", "e" +# attachPoint Used for "n" and "s" sides only. Zero means return X position of the centre position of the side. +# A number above zero means return the X position of a point along the side starting from its leftmost +# edge and increasing equi-distantly as the number increases. Used for drawing starting and ending +# points of a "long" line. +# + +sub objectSideX($$$) + { + my ($obj,$dir,$attachPoint) = @_; + if ($dir eq "n" || $dir eq "s") { + if ($attachPoint == 0) + { + return objectX($obj) + ($rectangleWidth/2); + } + else + { + # BUG - can overlap with attachPoint 0 !! + return objectX($obj) + ($attachPoint * $tabSpacing); + } + } + elsif ($dir eq "w") { + return objectX($obj); + } + elsif ($dir eq "e") { + return objectX($obj) + $rectangleWidth; + } + else { + die "objectSideX: Bad argument (dir=$dir)\n"; + } + + } + +sub objectSideY($$$) + { + my ($obj,$dir,$attachPoint) = @_; + if ($dir eq "w" || $dir eq "e") { + if ($attachPoint == 0) + { + return objectY($obj) + ($rectangleHeight/2); + } + else + { + # BUG - can overlap with attachPoint 0 !! + return objectY($obj) + ($attachPoint * 10); + } + } + elsif ($dir eq "n") { + return objectY($obj); + } + elsif ($dir eq "s") { + return objectY($obj) + $rectangleHeight; + } + else { + die "objectSideY: Bad argument (dir=$dir)\n"; + } + + } + +# +# Return the X position of the leftmost side of an object. +# Calculated by taking the X position of the supergrid column that the object is within +# and adding this to the column within the subgrid that the object occupies, and then +# multiplying by the subgrid spacing. +# +# +# Arguments: +# obj object in question +# + +sub objectX($) + { + my $obj = $_[0]; + return gridX($obj->{col}) + $obj->{subCol} * $rectangleWidthWithMargin; + } + +sub objectY($) + { + my $obj = $_[0]; + return gridY($obj->{row}) + $obj->{subRow} * $rectangleHeightWithMargin; + } + +sub objectZ($) + { + my $obj = $_[0]; + # Find number of objects at [col,row]) + my $objects = $subGridPlacement[$obj->{col}][$obj->{row}]{subZ} + 1; + # Calculate total depth to be occupied by these objects and spacing betwen them + my $depthZ = ($objects-1) * $boxDepthWithMargin + $boxDepth; + + return ($depthZ/2) - ($obj->{subZ} * $boxDepthWithMargin); + } + +sub normaliseCoords() + { + foreach $group (keys %groupRanges) { + my $rangeRef = $groupRanges{$group}; + + print "group $group, rangeRef $rangeRef\n"; + + my $x = $rangeRef->{minCol}; + my $y = $rangeRef->{minRow}; + + $rangeRef->{minCol} = 0; + $rangeRef->{minRow} = 0; + $rangeRef->{maxCol} -= $x; + $rangeRef->{maxRow} -= $y; + + foreach my $obj (@objects) { + if ($obj->{group} == $group) { + $obj->{col} -= $x; + $obj->{row} -= $y; + } + } + } + } + +sub seperateOverlaps() + { + my $obj; + foreach $obj (@objects) { + my $w_e_Relations = defined($obj->{w}) || defined($obj->{e}); + my $n_s_Relations = defined($obj->{n}) || defined($obj->{s}); + my $col = $obj->{col}; + my $row = $obj->{row}; + + # only deal with objects that have relationships to other objects + if ($w_e_Relations || $n_s_Relations) { + if (!defined($subGridPlacement[$col][$row]{subCol})) { + $subGridPlacement[$col][$row]{subCol} = 0; + $subGridPlacement[$col][$row]{subRow} = 0; + $subGridPlacement[$col][$row]{subZ} = 0; # VRML +# print "subGridPlacement $obj->{name} [ $col, $row ] zeroed\n"; + } + else { + if ($outputFormat eq "svg") { + if ($w_e_Relations) { + ++$subGridPlacement[$col][$row]{subRow}; +# print "subGridPlacement $obj->{name} [ $col, $row ] subRow = ", $subGridPlacement[$col][$row]{subRow},"\n"; + } + if ($n_s_Relations) { + ++$subGridPlacement[$col][$row]{subCol}; +# print "subGridPlacement $obj->{name} [ $col, $row ] subCol = ", $subGridPlacement[$col][$row]{subCol},"\n"; + } + } + else { + ++$subGridPlacement[$col][$row]{subZ}; + } + } + + $obj->{subZ} = $subGridPlacement[$col][$row]{subZ}; # VRML + $obj->{subCol} = $subGridPlacement[$col][$row]{subCol}; + $obj->{subRow} = $subGridPlacement[$col][$row]{subRow}; + + $rowToSubGridHeight[$row] = max($rowToSubGridHeight[$row], $obj->{subRow} + 1); + $colToSubGridWidth[$col] = max($colToSubGridWidth[$col], $obj->{subCol} + 1); + } + } + + } + +# +# Ensure that each distinct group is displayed in its own area of the canvas vertically. +# Go through and offset vertically each of the objects in the group. Also, initialise +# the {row} for each group in the groupRange to contain the row that the group starts at. +# +sub seperateGroups() + { + my $debug = 1; + if ($debug) { print "\nseperateGroups(): g = $g\n"; } + my $row = 0; + foreach $group (keys %groupRanges) + { + if ($row != 0) + { + foreach my $obj (@objects) + { + if ($obj->{group} == $group) + { + $obj->{row} += $row; + } + } + } + + my $ref = $groupRanges{$group}; + if ($debug) { print "$group:\trow $row, height: ",$ref->{maxRow} - $ref->{minRow}, "\n"; } + $ref->{row} = $row; + $row += $ref->{maxRow} - $ref->{minRow} + $groupVerticalSeperation; + } + } + +sub calculateGridPositions() + { + my $total = 0; + my $last; + for (my $i = 0 ; $i < scalar(@rowToSubGridHeight) ; ++$i) { + $last = $rowToSubGridHeight[$i]; + $rowToSubRowIndex[$i] = $total; + $total += $last; + } + + $total = 0; + for (my $i = 0 ; $i < scalar(@colToSubGridWidth) ; ++$i) { + $last = $colToSubGridWidth[$i]; + $colToSubColIndex[$i] = $total; + $total += $last; + } + + } + + +sub hasRelationships($) + { + my $objRef = $_[0]; + if ($objRef->{n} || $objRef->{s} || $objRef->{w} || $objRef->{e}) { + return 1; + } + else { + return 0; + } + } + +sub isRelated($$) +{ + my ($srcRef, $destRef) = @_; + my $obj; + foreach my $dir ( "n", "s", "e", "w" ) { + if (exists $srcRef->{$dir}) { + foreach $obj (@{$srcRef->{$dir}}) { + if ($obj == $destRef) { + return 1; + } + } + } + } + return 0; +} + + +sub max($$) + { + my ($a,$b) = @_; + return $a > $b ? $a : $b; + } + +sub printArray($$) + { + my ($name,$array) = @_; + print $name, " (size ", scalar(@{$array}), "): "; + foreach my $i (@{$array}) { + print $i, ", "; + } + print "\n\n"; + } + +sub printHash($$) + { + my ($name,$hash) = @_; + if ($name ne "") + { + print $name, ":\t"; + } + else + { + print "\t"; + } + + foreach my $i (keys %{$hash}) { + print $i, " = ", $hash->{$i}, ", "; + } + } + +sub printGroupRanges() + { + print "\n%groupRanges:\n"; + foreach my $i (keys %groupRanges) { + printHash($i, $groupRanges{$i}); + print "\n"; + } + print "\n"; + } + +sub print2DArray($$) + { + my ($name,$array) = @_; + print $name, ":\n"; + my $index1 = 0; + my $index2 = 0; + foreach my $i (@{$array}) { + print "$index1:\n"; + $index2 = 0; + foreach my $j (@{$i}) { + print "\t"; + printHash($index2, $j); + print "\n"; + ++$index2; + } + ++$index1; + print "\n"; + } + print "\n"; + } + +sub getTokens($) + { + my $stringRef = $_[0]; + $$stringRef =~ s/^\s*//; + $$stringRef =~ s/\s*$//; + if ($$stringRef !~ /;/) { + my $string = $$stringRef; + $$stringRef = ""; + return $string; + } + $$stringRef =~ s/^(.*?)\s*;\s*//; + return $1; + } + +# +# VRML Animation Routines +# + +sub hasVrmlTokens($) + { + my $stringRef = $_[0]; + if ($$stringRef =~ s/^vrml: //) { + return 1; + } + else { + return 0; + } + } + +# +# Given two strings, "X" and "Y", return a string of the form "X_Y" where +# "X" is the lexically lesser in rank. +# +# For example: "A1" and "B2" yields "A1_B2". The idea is to form a predictable +# and unique relationship name between two named nodes, where there are normally +# two possibilities (i.e. "A1_B2" and "B2_A1"). + +sub vrmlRelationshipName($$) + { + my ($name1, $name2) = @_; + return $name1 lt $name2 ? $name1 . "_" . $name2 : $name2 . "_" . $name1; + } +# +# Animation Glossary: +# +# "Node" A mesh node (e.g. "IPCPr"). Graphically represented as a box with text on front and back. +# "Relationship" A relationship between two nodes (e.g. "IPCPr_NetMCPr"). Graphically represented as a thin cylinder connecting the nodes. +# "Animate" To make a node or relationship visible. +# "DeAnimate" To make a node or relationship invisible. +# + +# +# Two object associated with animation: +# @animateSequence +# Array indicating the animation sequence. Each entry is a hash: +# Hash keys present for nodes and relationships: +# {action} => (integer) 0 to make node/relationship invisible, 1 to make it visible. +# +# Hash keys present only for nodes: +# {object} => (ref) reference into @objects entry for the node. +# +# Hash keys present only for relationships: +# {name} => (string) name of the relationship. +# {srcObj} => (ref) reference into @objects for source node. +# {destObj} => (ref) reference into @objects for destination node. +# +# %animateVisible +# Hash indicating which nodes or relationships are currently on display at the current point in the +# animation sequence. +# +# {} = name of node or relationship, which is a hash: +# +# Keys present for nodes and relationships: +# {visible} => (integer). 1 if node or relationship is displayed, 0 if node or relationship is no longer displayed. +# +# Hash keys present only for nodes: +# {related} => (hash). Keys within {related} are hash are names of nodes that are +# related to this node and whose relationships to this node are visible. + +sub Animate($$$$) + { + my ($name, $object,$srcObject,$destObject) = @_; + die "Animate: null name" if (!$name); + + if ($object) { + # animate a node + $animateVisible{$name} = { visible => 1, related => {} }; # "related" = hash of names of other objects this object has a relationship with. + push @animateSequence, { object => $object, action => 1 }; + } + else { + # animate a relationship between nodes + $animateVisible{$name} = { visible => 1 }; + push @animateSequence, { srcObj => $srcObject, destObj => $destObject, name => $name, action => 1 }; + # for the two nodes, store the fact that each node has a relationship to the other + my $destName = $destObject->{Name}; + my $srcName = $srcObject->{Name}; + $animateVisible{$srcName}{related}{$destName} = 1; + $animateVisible{$destName}{related}{$srcName} = 1; + } + } + +sub CheckAndAnimate($$$$) + { + my ($name,$object,$srcObject,$destObject) = @_; + + # We only want to animate if the object is not already visible. Note that this is not the same the hash entry + # existing with $animateVisible{$name}{visible} set to 0, which means that it was animated and then de-animated. + # In this case, we would not want to re-animate the object, as it shouldn't be possible for the same node to + # be re-created (although relationships are assumed to be terminated once and never re-created between the same + # two nodes). + if (! exists($animateVisible{$name})) { + Animate($name, $object, $srcObject, $destObject); + return 1; + } + return 0; + } + +sub CheckAndDeAnimate($$$$) +{ + my ($name, $objRef, $srcObject, $destObject) = @_; + # Only de-animate if the object is visible (i.e. $animateVisible{$name} exists and $animateVisible{$name}{visible} is non-zero). + if ($animateVisible{$name}{visible}) { + if ($objRef) { + # de-animate out relationships to this nodes that are still visible + # (i.e. because the appropriate "ClientLeaving" etc message has not been seen) + foreach my $relatedNode (keys %{$animateVisible{$name}{related}}) { + die "CheckAndDeAnimate: overwriting destObject" if $destObject != 0; + my $destObj = findObject($relatedNode); + my $relationshipName = vrmlRelationshipName($objRef->{Name}, $destObj->{Name}); + if ($animateVisible{$relationshipName}{visible}) { + push @animateSequence, { srcObj => $objRef, destObj => $destObj, name => $relationshipName, action => 0 }; + $animateVisible{$relationshipName}{visible} = 0; + } + } + push @animateSequence, { object => $objRef, action => 0 }; + } else { + push @animateSequence, { srcObj => $srcObject, destObj => $destObject, name => $name, action => 0 }; + my $destName = $destObject->{Name}; + my $srcName = $srcObject->{Name}; + delete $animateVisible{$srcName}{related}{$destName}; + delete $animateVisible{$destName}{related}{$srcName}; + } + $animateVisible{$name}{visible} = 0; + } +} + +# +# Called when a message is exchanged between two nodes. +# +sub AnimateObjectMessage($$$) +{ + my ($srcRef, $destRef, $direction) = @_; + if ($srcRef != $destRef) { + my $srcName = $srcRef->{Name}; + my $destName = $destRef->{Name}; + my $relationshipName = vrmlRelationshipName($srcName, $destName); + if ($direction && $direction eq "~") { + # Relationship terminated + CheckAndDeAnimate($relationshipName, 0, $srcRef, $destRef); + } else { + CheckAndAnimate($relationshipName, 0, $srcRef, $destRef); + } + } +} + +# +# Called when an node is created +# +sub AnimateObjectCreate($) +{ + my ($objRef) = @_; + my $objectName = $objRef->{Name}; + CheckAndAnimate($objectName, $objRef, 0, 0); +} + +# +# Called when a node is destroyed +# +sub AnimateObjectDestroy($) + { + my ($objRef) = @_; + my $objectName = $objRef->{Name}; + CheckAndDeAnimate($objectName, $objRef, 0, 0); + } + +sub AnimateDo() +{ + AnimateVrmlPrologue(); + AnimateVrmlSequencerPart1(); + + # Translate the animations we have built up in @animateSequence array into two integer arrays in the VRML file: + # + # action[] Action to perform. Each entry is an integer encoded as: + # ( << 8) | + # Where: = 1 to make VRML elements visible or 0 to make them invisible. + # The VRML elements themselves are contained in the VRML "nodes" array. + # + # nodes[] VRML elements to animate. Each entry is a VRML SFNode reference. + # + # The action[] array is executed in sequence. Each entry in the action[] array corresponds to one or more + # entries in the nodes[] array. Each array has an iterator - the action[] iterator always increments one + # at a time. The nodes[] iterator increments by the number of VRML elements that the corresponding action[] + # entry needs to iterate. Thus, for each action[] entry there are one or more nodes[] entries: + # For example: + # action[0] = 1<<8 | ... : nodes[0] + # action[1] = 3<<8 | ... nodes[1] nodes[2] nodes[3] + # action[2] = 1<<8 | ... nodes[4] + # action[3] = 1<<8 | ... nodes[5] + # action[4] = 2<<8 | ... nodes[6] nodes[7] + # etc + my $name; + foreach my $seq (@animateSequence) { + if (exists $seq->{object}) { + # animate/de-animate a node + if (hasRelationships($seq->{object})) { + print WRL "USE $seq->{object}{Name},\n"; # VRML box + print WRL "USE $seq->{object}{Name}_TF,\n"; # VRML text at front + print WRL "USE $seq->{object}{Name}_TB,\n"; # VRML text at back + } + } else { + # animate/de-animate a relationship between nodes + if (isRelated($seq->{srcObj}, $seq->{destObj})) { + print WRL "USE $seq->{name},\n"; # VRML thin cylinder + } + } + } + AnimateVrmlSequencerPart2(); + foreach my $seq (@animateSequence) { + if (exists $seq->{object}) { + if (hasRelationships($seq->{object})) { + print "Action $seq->{action}, $seq->{object}{Name}\n"; + print WRL $seq->{action} + (3 << 8), ",\n"; + } + } else { + if (isRelated($seq->{srcObj}, $seq->{destObj})) { + print "Action $seq->{action}, $seq->{name}\n"; + print WRL $seq->{action} + (1 << 8), ",\n"; + } + } + } + AnimateVrmlSequencerPart3(); +} + +sub AnimateVrmlPrologue() + { + ########################### + # Begin Interpolated Text # + ########################### + print WRL<> 8) & 0xff; + var act = action[actionIndex] & 0xff; + if (act == 1) { + for (var i = 0 ; i < count ; ++i) { + Browser.addRoute(invertNode[i], 'value_changed', nodes[nodeIndex++], 'set_transparency'); + } + for (var i = 0 ; i < count ; ++i) { + timeNode[i].startTime = time; + } + } else { + for (var i = 0 ; i < count ; ++i) { + Browser.addRoute(timeNode2[i], 'fraction_changed', nodes[nodeIndex++], 'set_transparency'); + } + for (var i = 0 ; i < count ; ++i) { + timeNode2[i].startTime = time; + } + } + pending = count; + actionIndex++; + } + + function timeCycleTime(value, time) { + } + + function timeIsActive1(value, time) { + timeIsActive(value, time, 0); + } + + function timeIsActive2(value, time) { + timeIsActive(value, time, 1); + } + + function timeIsActive3(value, time) { + timeIsActive(value, time, 2); + } + + function timeIsActive(value, time, which) { + if (value == false) { + if (actionIndex > 0) { + var count = (action[actionIndex-1] >> 8) & 0xff; + var act = action[actionIndex-1] & 0xff; + var j = nodeIndex - count + which; + + if (act == 1) { + Browser.deleteRoute(invertNode[which], 'value_changed', nodes[j], 'set_transparency'); + nodes[j].transparency = 0; + } else { + Browser.deleteRoute(timeNode2[which], 'fraction_changed', nodes[j], 'set_transparency'); + nodes[j].transparency = 1; + } + } + if (--pending == 0 && actionIndex < action.length) { + startNextNode(time); + } + } + } + " +} + +ROUTE START.cycleTime TO SEQ.startTime +#ROUTE TIME.fraction TO INVERT.fraction + +ROUTE TIME1.fraction TO INVERT1.fraction +ROUTE TIME2.fraction TO INVERT2.fraction +ROUTE TIME3.fraction TO INVERT3.fraction + +#ROUTE TIME.cycleTime TO SEQ.timeCycleTime + +ROUTE TIME1.isActive TO SEQ.timeIsActive1 +ROUTE TIME2.isActive TO SEQ.timeIsActive2 +ROUTE TIME3.isActive TO SEQ.timeIsActive3 + +ROUTE TIME4.isActive TO SEQ.timeIsActive1 +ROUTE TIME5.isActive TO SEQ.timeIsActive2 +ROUTE TIME6.isActive TO SEQ.timeIsActive3 + +END + ######################### + # End Interpolated Text # + ######################### + }