From f90dd4ffc4b679e61a2a8cf43853b7d3c72c3e83 Mon Sep 17 00:00:00 2001 From: randomguy3 Date: Tue, 17 Jan 2012 18:39:31 +0000 Subject: Calculate the head and tail of edges to be just where they contact the node (ie: behave more like tikz). git-svn-id: https://tikzit.svn.sourceforge.net/svnroot/tikzit/trunk@388 7c02a99a-9b00-45e3-bf44-6f3dd7fddb64 --- tikzit/NEWS | 2 + tikzit/src/common/Edge.h | 26 +- tikzit/src/common/Edge.m | 43 ++- tikzit/src/common/Node.m | 14 +- tikzit/src/common/test/Makefile | 14 + tikzit/src/common/test/color.m | 6 +- tikzit/src/common/test/common.m | 4 +- tikzit/src/common/test/maths.m | 562 +++++++++++++++++++++++++++++++++++++++ tikzit/src/common/test/parser.m | 7 +- tikzit/src/common/test/test.h | 16 ++ tikzit/src/common/test/test.m | 79 +++++- tikzit/src/common/util.h | 47 +++- tikzit/src/common/util.m | 147 +++++++++- tikzit/src/linux/Edge+Render.m | 8 +- tikzit/src/linux/GraphRenderer.m | 8 +- tikzit/src/linux/Node+Render.h | 4 +- tikzit/src/linux/Node+Render.m | 31 ++- 17 files changed, 975 insertions(+), 43 deletions(-) create mode 100644 tikzit/src/common/test/Makefile create mode 100644 tikzit/src/common/test/maths.m diff --git a/tikzit/NEWS b/tikzit/NEWS index 06fe57d..33686b5 100644 --- a/tikzit/NEWS +++ b/tikzit/NEWS @@ -6,6 +6,8 @@ tikzit 0.8 (2012-01-??): * Add support for multiple custom preambles * The path to pdflatex is now configurable * Make everything look a bit better + * Edges now start from the edge of a node, not the centre, + which is what tikz does tikzit 0.7 (2011-12-06): * Add bounding box support diff --git a/tikzit/src/common/Edge.h b/tikzit/src/common/Edge.h index 6069b9f..b92d7a7 100644 --- a/tikzit/src/common/Edge.h +++ b/tikzit/src/common/Edge.h @@ -141,15 +141,37 @@ typedef enum { */ @property (assign) EdgeBendMode bendMode; +/*! + @property head + @brief The starting point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. It is where the edge + makes contact with the source node. + */ +@property (readonly) NSPoint head; + +/*! + @property tail + @brief The ending point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. It is where the edge + makes contact with the target node. + */ +@property (readonly) NSPoint tail; + /*! @property cp1 - @brief The first control point. Computed from the source, target, and bend. + @brief The first control point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. */ @property (readonly) NSPoint cp1; /*! @property cp2 - @brief The second control point. Computed from the source, target, and bend. + @brief The second control point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. */ @property (readonly) NSPoint cp2; diff --git a/tikzit/src/common/Edge.m b/tikzit/src/common/Edge.m index efd9707..1878165 100644 --- a/tikzit/src/common/Edge.m +++ b/tikzit/src/common/Edge.m @@ -22,6 +22,7 @@ // #import "Edge.h" +#import "Shape.h" #import "util.h" @implementation Edge @@ -73,7 +74,37 @@ } - (NSPoint) _findContactPointOn:(Node*)node at:(float)angle { - return [node point]; + NSPoint rayStart = [node point]; + Shape *shape = [node shape]; + if (shape == nil) { + return rayStart; + } + + Transformer *shapeTrans = [node shapeTransformer]; + NSRect searchArea = [node boundsUsingShapeTransform:shapeTrans]; + if (!NSPointInRect(rayStart, searchArea)) { + return rayStart; + } + + NSPoint rayEnd = findExitPointOfRay (rayStart, angle, searchArea); + + for (NSArray *path in [shape paths]) { + for (Edge *curve in path) { + NSPoint intersect; + [curve updateControls]; + if (lineSegmentIntersectsBezier (rayStart, rayEnd, + [shapeTrans toScreen:curve->head], + [shapeTrans toScreen:curve->cp1], + [shapeTrans toScreen:curve->cp2], + [shapeTrans toScreen:curve->tail], + &intersect)) { + // we just keep shortening the line + rayStart = intersect; + } + } + } + + return rayStart; } - (void)updateControls { @@ -190,6 +221,16 @@ return NSMakePoint(mid.x - (mid.y - midTan.y), mid.y + (mid.x - midTan.x)); } +- (NSPoint) head { + [self updateControls]; + return head; +} + +- (NSPoint) tail { + [self updateControls]; + return tail; +} + - (NSPoint)cp1 { [self updateControls]; return cp1; diff --git a/tikzit/src/common/Node.m b/tikzit/src/common/Node.m index 8880826..136fe20 100644 --- a/tikzit/src/common/Node.m +++ b/tikzit/src/common/Node.m @@ -24,7 +24,6 @@ #import "Node.h" #import "Shape.h" -#import "ShapeNames.h" @implementation Node @@ -48,7 +47,7 @@ if (style) { return [Shape shapeForName:[style shapeName]]; } else { - return [Shape shapeForName:SHAPE_CIRCLE]; + return nil; } } @@ -74,10 +73,13 @@ } - (NSRect) boundsUsingShapeTransform:(Transformer*)shapeTrans { - float strokeThickness = style ? [style strokeThickness] : [NodeStyle defaultStrokeThickness]; - NSRect screenBounds = [shapeTrans rectToScreen:[[self shape] boundingRect]]; - screenBounds = NSInsetRect(screenBounds, -strokeThickness, -strokeThickness); - return screenBounds; + //if (style) { + return [shapeTrans rectToScreen:[[self shape] boundingRect]]; + /*} else { + NSRect r = NSZeroRect; + r.origin = [shapeTrans toScreen:[self point]]; + return r; + }*/ } - (NSRect) boundingRect { diff --git a/tikzit/src/common/test/Makefile b/tikzit/src/common/test/Makefile new file mode 100644 index 0000000..d158d16 --- /dev/null +++ b/tikzit/src/common/test/Makefile @@ -0,0 +1,14 @@ +OBJC = gcc -MMD -MP -DSTAND_ALONE -DGNUSTEP -DGNUSTEP_BASE_LIBRARY=1 -DGNU_RUNTIME=1 -DGNUSTEP_BASE_LIBRARY=1 -fno-strict-aliasing -fPIC -Wall -DGSWARN -DGSDIAGNOSE -Wno-import -O0 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -fgnu-runtime -fconstant-string-class=NSConstantString -I. -I.. -I/users/alemer/GNUstep/Library/Headers -std=c99 -D_GNU_SOURCE -rdynamic -fgnu-runtime -L/users/alemer/GNUstep/Library/Libraries -L/usr/local/lib64 -L/usr/lib64 -lgnustep-base -lpthread -lobjc -lm + +maths_test_objects = test.m maths.m ../util.m +color_test_objects = test.m color.m ../ColorRGB.m ../util.m ../BasicMapTable.m ../RColor.m + +test: maths-test color-test + ./maths-test + ./color-test + +maths-test: $(maths_test_objects) + $(OBJC) $(maths_test_objects) -o $@ + +color-test: $(color_test_objects) + $(OBJC) $(color_test_objects) -o $@ diff --git a/tikzit/src/common/test/color.m b/tikzit/src/common/test/color.m index e7a80ba..48a6ff4 100644 --- a/tikzit/src/common/test/color.m +++ b/tikzit/src/common/test/color.m @@ -23,7 +23,11 @@ #import "test/test.h" #import "ColorRGB.h" +#ifdef STAND_ALONE +void runTests() { +#else void testColor() { +#endif NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; startTestBlock(@"color"); @@ -73,4 +77,4 @@ void testColor() { endTestBlock(@"color"); [pool drain]; -} \ No newline at end of file +} diff --git a/tikzit/src/common/test/common.m b/tikzit/src/common/test/common.m index ee6cb5f..c9ac980 100644 --- a/tikzit/src/common/test/common.m +++ b/tikzit/src/common/test/common.m @@ -23,10 +23,12 @@ #import "test/test.h" void testParser(); void testColor(); +void testMaths(); void testCommon() { startTestBlock(@"common"); testParser(); testColor(); + testMaths(); endTestBlock(@"common"); -} \ No newline at end of file +} diff --git a/tikzit/src/common/test/maths.m b/tikzit/src/common/test/maths.m new file mode 100644 index 0000000..a11e58e --- /dev/null +++ b/tikzit/src/common/test/maths.m @@ -0,0 +1,562 @@ +// +// TikZiT +// +// Copyright 2011 Alex Merry +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see . +// + +#import "../util.h" + +#import "test.h" + +void testRectAroundPoints() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"NSRectAroundPoints"); + + NSRect rect = NSRectAroundPoints (NSZeroPoint, NSZeroPoint); + assertRectsEqual (@"(0,0) and (0,0)", rect, NSZeroRect); + + rect = NSRectAroundPoints (NSZeroPoint, NSMakePoint (1.0f, 1.0f)); + assertRectsEqual (@"(0,0) and (1,1)", rect, NSMakeRect (0.0f, 0.0f, 1.0f, 1.0f)); + + rect = NSRectAroundPoints (NSMakePoint (-1.0f, 1.0f), NSMakePoint (1.0f, -1.0f)); + assertRectsEqual (@"(-1,1) and (1,-1)", rect, NSMakeRect (-1.0f, -1.0f, 2.0f, 2.0f)); + + endTestBlock(@"NSRectAroundPoints"); + [pool drain]; +} + +void testRectAroundPointsWithPadding() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"NSRectAroundPointsWithPadding"); + + NSRect rect = NSRectAroundPointsWithPadding (NSZeroPoint, NSZeroPoint, 0.0f); + assertRectsEqual (@"(0,0) and (0,0); 0 padding", rect, NSZeroRect); + + rect = NSRectAroundPointsWithPadding (NSZeroPoint, NSZeroPoint, 0.2f); + assertRectsEqual (@"(0,0) and (0,0); 0.2 padding", rect, NSMakeRect (-0.2f, -0.2f, 0.4f, 0.4f)); + + rect = NSRectAroundPointsWithPadding (NSZeroPoint, NSMakePoint (1.0f, 1.0f), -0.2f); + assertRectsEqual (@"(0,0) and (1,1); -0.2 padding", rect, NSMakeRect (0.2f, 0.2f, 0.6f, 0.6f)); + + endTestBlock(@"NSRectAroundPointsWithPadding"); + [pool drain]; +} + +void testGoodAtan() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"good_atan"); + + assertFloatsEqual (@"0.0, 0.0", good_atan (0.0f, 0.0f), 0.0f); + assertFloatsEqual (@"0.0, 1.0", good_atan (0.0f, 1.0f), 0.5f * M_PI); + assertFloatsEqual (@"0.0, -1.0", good_atan (0.0f, -1.0f), 1.5f * M_PI); + assertFloatsEqual (@"1.0, 0.0", good_atan (1.0f, 0.0f), 0.0f); + assertFloatsEqual (@"1.0, 0.1", good_atan (1.0f, 0.1f), 0.0996687f); + + endTestBlock(@"good_atan"); + [pool drain]; +} + +void testBezierInterpolate() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"bezierInterpolate"); + + assertFloatsEqual (@"0.0, (0.0, 0.1, 0.2, 0.3)", bezierInterpolate (0.0f, 0.0f, 0.1f, 0.2f, 0.3f), 0.0f); + assertFloatsEqual (@"1.0, (0.0, 0.1, 0.2, 0.3)", bezierInterpolate (1.0f, 0.0f, 0.1f, 0.2f, 0.3f), 0.3f); + assertFloatsEqual (@"0.5, (0.0, 0.1, 0.2, 0.3)", bezierInterpolate (0.5f, 0.0f, 0.1f, 0.2f, 0.3f), 0.15f); + // FIXME: other tests + + endTestBlock(@"bezierInterpolate"); + [pool drain]; +} + +void testLineSegmentsIntersect() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"lineSegmentsIntersect"); + + BOOL result = NO; + NSPoint intersection = NSMakePoint (-1.0f, -1.0f); + + result = lineSegmentsIntersect (NSMakePoint (-1.0f, -1.0f), NSMakePoint (1.0f, 1.0f), + NSMakePoint (-1.0f, 1.0f), NSMakePoint (1.0f, -1.0f), + &intersection); + TEST (@"Cross at zero: has intersection", result); + assertPointsEqual (@"Cross at zero: intersection value", intersection, NSZeroPoint); + + result = lineSegmentsIntersect (NSMakePoint (-1.0f, -1.0f), NSMakePoint (-0.5f, -0.5f), + NSMakePoint (-1.0f, 1.0f), NSMakePoint (1.0f, -1.0f), + &intersection); + TEST (@"Fail to cross at zero", !result); + + result = lineSegmentsIntersect (NSMakePoint (1.0f, 1.0f), NSMakePoint (1.0f, -1.0f), + NSMakePoint (0.0f, 0.0f), NSMakePoint (1.0f, 0.0f), + &intersection); + TEST (@"Touch at one: has intersection", result); + assertPointsEqual (@"Touch at one: intersection value", intersection, NSMakePoint (1.0f, 0.0f)); + + endTestBlock(@"lineSegmentsIntersect"); + [pool drain]; +} + +void testLineSegmentIntersectsRect() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"lineSegmentIntersectsRect"); + + BOOL result = NO; + + result = lineSegmentIntersectsRect ( + NSMakePoint (-1.0f, -1.0f), + NSMakePoint (0.0f, 0.0f), + NSZeroRect); + TEST (@"Zero rect; line touches zero", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (-1.0f, -1.0f), + NSMakePoint (-0.1f, -0.1f), + NSZeroRect); + TEST (@"Zero rect; line short of zero", !result); + + NSRect rect = NSMakeRect (1.0f, 1.0f, 1.0f, 1.0f); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 0.0f), + NSMakePoint (3.0f, 1.0f), + rect); + TEST (@"Line underneath", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 0.0f), + NSMakePoint (1.0f, 3.0f), + rect); + TEST (@"Line to left", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 2.0f), + NSMakePoint (3.0f, 3.0f), + rect); + TEST (@"Line above", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (2.0f, 0.0f), + NSMakePoint (3.0f, 3.0f), + rect); + TEST (@"Line to right", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 0.0f), + NSMakePoint (0.9f, 0.9f), + rect); + TEST (@"Line short", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.1f, 1.1f), + NSMakePoint (1.9f, 1.9f), + rect); + TEST (@"Line inside", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 1.5f), + NSMakePoint (3.0f, 1.5f), + rect); + TEST (@"Horizontal line through", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.5f, 0.0f), + NSMakePoint (1.5f, 3.0f), + rect); + TEST (@"Vertical line through", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.5f, 1.0f), + NSMakePoint (2.0f, 2.5f), + rect); + TEST (@"Cut top and left", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (2.0f, 0.5f), + NSMakePoint (0.5f, 2.0f), + rect); + TEST (@"Cut bottom and left", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.0f, 0.5f), + NSMakePoint (2.5f, 2.0f), + rect); + TEST (@"Cut bottom and right", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 1.0f), + NSMakePoint (2.0f, 3.0f), + rect); + TEST (@"Touch top left", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.0f, 0.0f), + NSMakePoint (3.0f, 2.0f), + rect); + TEST (@"Touch bottom right", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.0f, 0.0f), + NSMakePoint (1.0f, 3.0f), + rect); + TEST (@"Along left side", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 1.0f), + NSMakePoint (3.0f, 1.0f), + rect); + TEST (@"Along bottom side", result); + + endTestBlock(@"lineSegmentIntersectsRect"); + [pool drain]; +} + +struct line_bezier_test { + NSString *msg; + NSPoint lstart; + NSPoint lend; + NSPoint c0; + NSPoint c1; + NSPoint c2; + NSPoint c3; + BOOL expectedResult; + float expectedT; + NSPoint expectedIntersect; +}; + +static struct line_bezier_test line_bezier_tests[] = { + { + @"Outside box", + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {0.0f, 1.0f}, + {0.0f, 2.0f}, + {1.0f, 2.0f}, + {1.0f, 1.0f}, + NO, + -1.0f, + {0.0f, 0.0f} + }, + { + @"Single intersect", + {100.0f, 20.0f}, + {195.0f, 255.0f}, + {93.0f, 163.0f}, + {40.0f, 30.0f}, + {270.0f, 115.0f}, + {219.0f, 178.0f}, + YES, + -0.4f, + {129.391693f, 92.705772f} + }, + { + @"Double intersect", + {100.0f, 20.0f}, + {195.0f, 255.0f}, + {93.0f, 163.0f}, + {40.0f, 30.0f}, + {270.0f, 115.0f}, + {154.0f, 212.0f}, + YES, + -0.909f, + {170.740646f,194.990021f} + }, + { + @"Near miss", + {100.0f, 20.0f}, + {195.0f, 255.0f}, + {93.0f, 163.0f}, + {40.0f, 30.0f}, + {176.0f, 100.0f}, + {154.0f, 212.0f}, + NO, + -1.0f, + {0.0f,0.0f} + } +}; +static unsigned int n_line_bezier_tests = sizeof (line_bezier_tests) / sizeof (line_bezier_tests[0]); + +void testLineSegmentIntersectsBezier() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"lineSegmentIntersectsBezier"); + + for (unsigned int i = 0; i < n_line_bezier_tests; ++i) { + NSPoint intersect; + BOOL result = lineSegmentIntersectsBezier ( + line_bezier_tests[i].lstart, + line_bezier_tests[i].lend, + line_bezier_tests[i].c0, + line_bezier_tests[i].c1, + line_bezier_tests[i].c2, + line_bezier_tests[i].c3, + &intersect); + if (result) { + if (line_bezier_tests[i].expectedT < 0.0f) { + assertPointsEqual (line_bezier_tests[i].msg, intersect, line_bezier_tests[i].expectedIntersect); + } else { + assertPointsEqual (line_bezier_tests[i].msg, intersect, + bezierInterpolateFull (line_bezier_tests[i].expectedT, line_bezier_tests[i].c0, line_bezier_tests[i].c1, line_bezier_tests[i].c2, line_bezier_tests[i].c3)); + } + } else { + if (line_bezier_tests[i].expectedResult) + fail (line_bezier_tests[i].msg); + else + pass (line_bezier_tests[i].msg); + } + } + +BOOL lineSegmentIntersectsBezier (NSPoint lstart, NSPoint lend, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3, NSPoint *result); + endTestBlock(@"lineSegmentIntersectsBezier"); + [pool drain]; +} + +struct exit_point_test { + NSString *msg; + NSPoint rayStart; + float angle; + NSRect rect; + NSPoint expected; +}; + +static struct exit_point_test exit_point_tests[] = { + { + @"0.0 rads", + {0.0f, 0.0f}, + 0.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, 0.0f} + }, + { + @"pi/2 rads", + {0.0f, 0.0f}, + M_PI / 2.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {0.0f, 1.0f} + }, + { + @"-pi/2 rads", + {0.0f, 0.0f}, + -M_PI / 2.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {0.0f, -1.0f} + }, + { + @"pi rads", + {0.0f, 0.0f}, + M_PI, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, 0.0f} + }, + { + @"-pi rads", + {0.0f, 0.0f}, + -M_PI, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, 0.0f} + }, + { + @"pi/4 rads", + {0.0f, 0.0f}, + M_PI / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, 1.0f} + }, + { + @"3pi/4 rads", + {0.0f, 0.0f}, + (3.0f * M_PI) / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, 1.0f} + }, + { + @"-pi/4 rads", + {0.0f, 0.0f}, + -M_PI / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, -1.0f} + }, + { + @"-3pi/4 rads", + {0.0f, 0.0f}, + (-3.0f * M_PI) / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, -1.0f} + }, + { + @"pi/8 rads", + {0.0f, 0.0f}, + M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, 0.414213562373095f} + }, + { + @"3pi/8 rads", + {0.0f, 0.0f}, + 3.0f * M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {0.414213562373095f, 1.0f} + }, + { + @"-5pi/8 rads", + {0.0f, 0.0f}, + -5.0f * M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-0.414213562373095f, -1.0f} + }, + { + @"-7pi/8 rads", + {0.0f, 0.0f}, + -7.0f * M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, -0.414213562373095f} + }, + { + @"pi/8 rads; origin (1,1)", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.0f, 0.0f}, {2.0f, 2.0f}}, + {2.0f, 1.414213562373095f} + }, + { + @"7pi/8 rads; origin (-2,2)", + {-2.0f, 2.0f}, + 7.0f * M_PI / 8.0f, + {{-3.0f, 1.0f}, {2.0f, 2.0f}}, + {-3.0f, 2.414213562373095f} + }, + { + @"pi/8 rads; origin (1,1); SW of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{1.0f, 1.0f}, {1.0f, 1.0f}}, + {2.0f, 1.414213562373095f} + }, + { + @"pi/8 rads; origin (1,1); SE of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.0f, 1.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); NE of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.0f, 1.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); NW of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{1.0f, 0.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); N of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.5f, 0.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"7pi/8 rads; origin (1,1); N of box", + {1.0f, 1.0f}, + 7.0f * M_PI / 8.0f, + {{0.5f, 0.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"-pi/8 rads; origin (1,1); S of box", + {1.0f, 1.0f}, + -M_PI / 8.0f, + {{0.5f, 1.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"-pi/8 rads; origin (1,1); E of box", + {1.0f, 1.0f}, + -M_PI / 8.0f, + {{0.0f, 0.5f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"-7pi/8 rads; origin (1,1); W of box", + {1.0f, 1.0f}, + -7.0f * M_PI / 8.0f, + {{1.0f, 0.5f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"7pi/8 rads; origin (1,1); W of box", + {1.0f, 1.0f}, + 7.0f * M_PI / 8.0f, + {{1.0f, 0.5f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); leave through top", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.9f, 0.1f}, {1.0f, 1.0f}}, + {1.2414213562373f, 1.1f} + }, + { + @"0 rads; origin (1,1); N of box", + {1.0f, 1.0f}, + 0.0f, + {{0.5f, 0.0f}, {1.0f, 1.0f}}, + {1.5f, 1.0f} + } +}; +static unsigned int n_exit_point_tests = sizeof (exit_point_tests) / sizeof (exit_point_tests[0]); + +void testFindExitPointOfRay() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"findExitPointOfRay"); + + for (unsigned int i = 0; i < n_exit_point_tests; ++i) { + NSPoint exitPoint = findExitPointOfRay ( + exit_point_tests[i].rayStart, + exit_point_tests[i].angle, + exit_point_tests[i].rect); + assertPointsEqual (exit_point_tests[i].msg, exitPoint, exit_point_tests[i].expected); + } + + endTestBlock(@"findExitPointOfRay"); + [pool drain]; +} + +#ifdef STAND_ALONE +void runTests() { +#else +void testMaths() { +#endif + startTestBlock(@"maths"); + testRectAroundPoints(); + testRectAroundPointsWithPadding(); + testGoodAtan(); + testBezierInterpolate(); + testLineSegmentsIntersect(); + testLineSegmentIntersectsRect(); + testFindExitPointOfRay(); + testLineSegmentIntersectsBezier(); + endTestBlock(@"maths"); +} + +// vim:ft=objc:ts=4:sts=4:sw=4:noet diff --git a/tikzit/src/common/test/parser.m b/tikzit/src/common/test/parser.m index fc9e76e..29dabe7 100644 --- a/tikzit/src/common/test/parser.m +++ b/tikzit/src/common/test/parser.m @@ -23,7 +23,12 @@ #import "test/test.h" #import "TikzGraphAssembler.h" + +#ifdef STAND_ALONE +void runTests() { +#else void testParser() { +#endif NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; startTestBlock(@"parser"); @@ -75,4 +80,4 @@ void testParser() { endTestBlock(@"parser"); [pool drain]; -} \ No newline at end of file +} diff --git a/tikzit/src/common/test/test.h b/tikzit/src/common/test/test.h index 4fddc92..59dcdd4 100644 --- a/tikzit/src/common/test/test.h +++ b/tikzit/src/common/test/test.h @@ -26,8 +26,17 @@ {} @end +BOOL fuzzyCompare (float f1, float f2); +BOOL fuzzyComparePoints (NSPoint p1, NSPoint p2); + void setColorEnabled(BOOL b); + +void pass(NSString *msg); +void fail(NSString *msg); void TEST(NSString *msg, BOOL test); +void assertRectsEqual (NSString *msg, NSRect val, NSRect exp); +void assertPointsEqual (NSString *msg, NSPoint val, NSPoint exp); +void assertFloatsEqual (NSString *msg, float val, float exp); void startTests(); void endTests(); @@ -39,3 +48,10 @@ void endTestBlock(NSString *name); NSString *_str = [[NSString alloc] initWithFormat:fmt, ##__VA_ARGS__]; \ printf("%s\n", [_str UTF8String]); \ [_str release]; } + +#define failFmt(fmt, ...) { \ + NSString *_fstr = [[NSString alloc] initWithFormat:fmt, ##__VA_ARGS__]; \ + fail(_fstr); \ + [_fstr release]; } + +// vim:ft=objc:ts=4:sts=4:sw=4:noet diff --git a/tikzit/src/common/test/test.m b/tikzit/src/common/test/test.m index 5e05c6e..8437646 100644 --- a/tikzit/src/common/test/test.m +++ b/tikzit/src/common/test/test.m @@ -57,13 +57,64 @@ static NSString *indents[6] = @end +BOOL fuzzyCompare(float f1, float f2) { + return (ABS(f1 - f2) <= 0.00001f * MAX(1.0f,MIN(ABS(f1), ABS(f2)))); +} + +BOOL fuzzyComparePoints (NSPoint p1, NSPoint p2) { + return fuzzyCompare (p1.x, p2.x) && fuzzyCompare (p1.y, p2.y); +} + +void pass(NSString *msg) { + PUTS(@"%@[%@PASS%@] %@", INDENT, GREEN, OFF, msg); + ++PASSES; +} + +void fail(NSString *msg) { + PUTS(@"%@[%@FAIL%@] %@", INDENT, RED, OFF, msg); + ++FAILS; +} + void TEST(NSString *msg, BOOL test) { if (test) { - PUTS(@"%@[%@PASS%@] %@", INDENT, GREEN, OFF, msg); - ++PASSES; + pass (msg); + } else { + fail (msg); + } +} + +void assertRectsEqual (NSString *msg, NSRect r1, NSRect r2) { + BOOL equal = fuzzyCompare (r1.origin.x, r2.origin.x) && + fuzzyCompare (r1.origin.y, r2.origin.y) && + fuzzyCompare (r1.size.width, r2.size.width) && + fuzzyCompare (r1.size.height, r2.size.height); + if (equal) { + pass (msg); + } else { + failFmt(@"%@ (expected (%f,%f:%fx%f) but got (%f,%f:%fx%f))", + msg, + r2.origin.x, r2.origin.y, r2.size.width, r2.size.height, + r1.origin.x, r1.origin.y, r1.size.width, r1.size.height); + } +} + +void assertPointsEqual (NSString *msg, NSPoint p1, NSPoint p2) { + BOOL equal = fuzzyCompare (p1.x, p2.x) && fuzzyCompare (p1.y, p2.y); + if (equal) { + pass (msg); } else { - PUTS(@"%@[%@FAIL%@] %@", INDENT, RED, OFF, msg); - ++FAILS; + failFmt(@"%@ (expected (%f,%f) but got (%f,%f)", + msg, + p2.x, p2.y, + p1.x, p1.y); + } +} + +void assertFloatsEqual (NSString *msg, float f1, float f2) { + if (fuzzyCompare (f1, f2)) { + pass (msg); + } else { + failFmt(@"%@ (expected %f but got %f", msg, f2, f1); } } @@ -102,3 +153,23 @@ void setColorEnabled(BOOL b) { OFF = @""; } } + +#ifdef STAND_ALONE +void runTests(); + +int main() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + setColorEnabled (YES); + startTests(); + + runTests(); + + endTests(); + + [pool drain]; + return 0; +} +#endif + +// vim:ft=objc:ts=4:sts=4:sw=4:noet diff --git a/tikzit/src/common/util.h b/tikzit/src/common/util.h index 82ba9d8..809e8ea 100644 --- a/tikzit/src/common/util.h +++ b/tikzit/src/common/util.h @@ -91,7 +91,18 @@ float good_atan(float dx, float dy); @param c3 the x (resp. y) coordinate of the end point. @result The x (resp. y) coordinate of the point at 'dist'. */ -float bezierInterpolate(float dist, float c0, float c1, float c2, float c3); +float bezierInterpolate (float dist, float c0, float c1, float c2, float c3); + +/*! + @brief Interpolate along a bezier curve to the given distance. + @param dist a distance from 0 to 1 spanning the whole curve. + @param c0 the x start point. + @param c1 the x first control point. + @param c2 the x second control point. + @param c3 the x end point. + @result The point at 'dist'. + */ +NSPoint bezierInterpolateFull (float dist, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3); /*! * @brief Find whether two line segments intersect @@ -102,7 +113,21 @@ float bezierInterpolate(float dist, float c0, float c1, float c2, float c3); * @param result A location to store the intersection point * @result YES if they intersect, NO if they do not */ -BOOL lineSegmentsIntersect(NSPoint l1start, NSPoint l1end, NSPoint l2start, NSPoint l2end, NSPoint *result); +BOOL lineSegmentsIntersect (NSPoint l1start, NSPoint l1end, NSPoint l2start, NSPoint l2end, NSPoint *result); + +/*! + * @brief Find whether a line segment intersects a bezier curve + * @detail Always finds the intersection furthest along the line segment + * @param lstart The starting point of the line segment + * @param lend The ending point of the line segment + * @param c0 The starting point of the bezier curve + * @param c1 The first control point of the bezier curve + * @param c2 The second control point of the bezier curve + * @param c3 The ending point of the bezier curve + * @param result A location to store the intersection point + * @result YES if they intersect, NO if they do not + */ +BOOL lineSegmentIntersectsBezier (NSPoint lstart, NSPoint lend, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3, NSPoint *result); /*! * @brief Find whether a line segment enters a rectangle @@ -111,7 +136,16 @@ BOOL lineSegmentsIntersect(NSPoint l1start, NSPoint l1end, NSPoint l2start, NSPo * @param rect The rectangle * @result YES if they intersect, NO if they do not */ -BOOL lineSegmentIntersectsRect(NSPoint lineStart, NSPoint lineEnd, NSRect rect); +BOOL lineSegmentIntersectsRect (NSPoint lineStart, NSPoint lineEnd, NSRect rect); + +/*! + * @brief Find where a ray exits a rectangle + * @param rayStart The starting point of the ray; must be contained in rect + * @param angle_rads The angle of the ray, in radians + * @param rect The rectangle + * @result The point at which the ray leaves the rect + */ +NSPoint findExitPointOfRay (NSPoint rayStart, float angle_rads, NSRect rect); /*! @brief Round val to nearest stepSize @@ -126,10 +160,15 @@ float roundToNearest(float stepSize, float val); float radiansToDegrees(float radians); /*! - @brief Normalises an angle (in degrees) to fall between -359 and 359 + @brief Normalises an angle (in degrees) to fall between -179 and 180 */ int normaliseAngleDeg (int degrees); +/*! + @brief Normalises an angle (in radians) to fall in the range (-pi,pi] + */ +float normaliseAngleRad (float rads); + /*! @brief Express a byte as alpha-only hex, with digits (0..16) -> (a..jA..F) @param sh A number 0-255 diff --git a/tikzit/src/common/util.m b/tikzit/src/common/util.m index 0445126..3dec971 100644 --- a/tikzit/src/common/util.m +++ b/tikzit/src/common/util.m @@ -82,17 +82,28 @@ float bezierInterpolate(float dist, float c0, float c1, float c2, float c3) { (dist*dist*dist) * c3; } -void lineCoeffsFromPoints(NSPoint p1, NSPoint p2, float *A, float *B, float *C) { +NSPoint bezierInterpolateFull (float dist, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3) { + return NSMakePoint (bezierInterpolate (dist, c0.x, c1.x, c2.x, c3.x), + bezierInterpolate (dist, c0.y, c1.y, c2.y, c3.y)); +} + +static void lineCoeffsFromPoints(NSPoint p1, NSPoint p2, float *A, float *B, float *C) { *A = p2.y - p1.y; *B = p1.x - p2.x; *C = (*A) * p1.x + (*B) * p1.y; } +static void lineCoeffsFromPointAndAngle(NSPoint p, float angle, float *A, float *B, float *C) { + *A = sin (angle); + *B = -cos (angle); + *C = (*A) * p.x + (*B) * p.y; +} + static BOOL lineSegmentContainsPoint(NSPoint l1, NSPoint l2, float x, float y) { - float maxX = l1.x > l2.x ? l2.x : l1.x; - float minX = l1.x > l2.x ? l1.x : l2.x; - float maxY = l1.y > l2.y ? l2.y : l1.y; - float minY = l1.y > l2.y ? l1.y : l2.y; + float minX = MIN(l1.x, l2.x); + float maxX = MAX(l1.x, l2.x); + float minY = MIN(l1.y, l2.y); + float maxY = MAX(l1.y, l2.y); return x >= minX && x <= maxX && y >= minY && y <= maxY; } @@ -123,6 +134,36 @@ BOOL lineSegmentsIntersect(NSPoint l1start, NSPoint l1end, NSPoint l2start, NSPo return NO; } +BOOL lineSegmentIntersectsBezier (NSPoint lstart, NSPoint lend, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3, NSPoint *result) { + NSRect curveBounds = NSRectAround4Points(c0, c1, c2, c3); + if (!lineSegmentIntersectsRect(lstart, lend, curveBounds)) + return NO; + + const int divisions = 20; + const float chunkSize = 1.0f/(float)divisions; + float chunkStart = 0.0f; + BOOL found = NO; + + for (int i = 0; i < divisions; ++i) { + float chunkEnd = chunkStart + chunkSize; + + NSPoint p1 = bezierInterpolateFull (chunkStart, c0, c1, c2, c3); + NSPoint p2 = bezierInterpolateFull (chunkEnd, c0, c1, c2, c3); + + NSPoint p; + if (lineSegmentsIntersect (lstart, lend, p1, p2, &p)) { + lstart = p; + found = YES; + } + + chunkStart = chunkEnd; + } + if (found && result) { + *result = lstart; + } + return found; +} + BOOL lineSegmentIntersectsRect(NSPoint lineStart, NSPoint lineEnd, NSRect rect) { const float rectMaxX = NSMaxX(rect); const float rectMinX = NSMinX(rect); @@ -167,6 +208,92 @@ BOOL lineSegmentIntersectsRect(NSPoint lineStart, NSPoint lineEnd, NSRect rect) return YES; } +NSPoint findExitPointOfRay (NSPoint p, float angle_rads, NSRect rect) { + const float rectMinX = NSMinX (rect); + const float rectMaxX = NSMaxX (rect); + const float rectMinY = NSMinY (rect); + const float rectMaxY = NSMaxY (rect); + + const float angle = normaliseAngleRad (angle_rads); + + // special case the edges + if (p.y == rectMaxY && angle > 0 && angle < M_PI) { + // along the top of the box + return p; + } + if (p.y == rectMinY && angle < 0 && angle > -M_PI) { + // along the bottom of the box + return p; + } + if (p.x == rectMaxX && angle > -M_PI/2.0f && angle < M_PI/2.0f) { + // along the right of the box + return p; + } + if (p.x == rectMinX && (angle > M_PI/2.0f || angle < -M_PI/2.0f)) { + // along the left of the box + return p; + } + + float A1, B1, C1; + lineCoeffsFromPointAndAngle(p, angle, &A1, &B1, &C1); + //NSLog(@"Ray is %fx + %fy = %f", A1, B1, C1); + + const float tlAngle = normaliseAngleRad (good_atan (rectMinX - p.x, rectMaxY - p.y)); + const float trAngle = normaliseAngleRad (good_atan (rectMaxX - p.x, rectMaxY - p.y)); + if (angle <= tlAngle && angle >= trAngle) { + // exit top + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMinX, rectMaxY), + NSMakePoint (rectMaxX, rectMaxY), + &A2, &B2, &C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } + + const float brAngle = normaliseAngleRad (good_atan (rectMaxX - p.x, rectMinY - p.y)); + if (angle <= trAngle && angle >= brAngle) { + // exit right + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMaxX, rectMaxY), + NSMakePoint (rectMaxX, rectMinY), + &A2, &B2, &C2); + //NSLog(@"Edge is %fx + %fy = %f", A2, B2, C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } + + const float blAngle = normaliseAngleRad (good_atan (rectMinX - p.x, rectMinY - p.y)); + if (angle <= brAngle && angle >= blAngle) { + // exit bottom + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMaxX, rectMinY), + NSMakePoint (rectMinX, rectMinY), + &A2, &B2, &C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } else { + // exit left + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMinX, rectMaxY), + NSMakePoint (rectMinX, rectMinY), + &A2, &B2, &C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } +} + float roundToNearest(float stepSize, float val) { if (stepSize==0.0f) return val; else return round(val/stepSize)*stepSize; @@ -186,6 +313,16 @@ int normaliseAngleDeg (int degrees) { return degrees; } +float normaliseAngleRad (float rads) { + while (rads > M_PI) { + rads -= 2 * M_PI; + } + while (rads <= -M_PI) { + rads += 2 * M_PI; + } + return rads; +} + static char ahex[] = {'a','b','c','d','e','f','g','h','i','j', 'A','B','C','D','E','F'}; diff --git a/tikzit/src/linux/Edge+Render.m b/tikzit/src/linux/Edge+Render.m index a6738b7..c64b35f 100644 --- a/tikzit/src/linux/Edge+Render.m +++ b/tikzit/src/linux/Edge+Render.m @@ -125,14 +125,14 @@ static const float cpLineWidth = 1.0; } - (void) createStrokePathInContext:(id)context withTransformer:(Transformer*)transformer { - NSPoint c_source = [transformer toScreen:src]; + NSPoint c_head = [transformer toScreen:head]; NSPoint c_cp1 = [transformer toScreen:cp1]; NSPoint c_cp2 = [transformer toScreen:cp2]; - NSPoint c_target = [transformer toScreen:targ]; + NSPoint c_tail = [transformer toScreen:tail]; [context startPath]; - [context moveTo:c_source]; - [context curveTo:c_target withCp1:c_cp1 andCp2:c_cp2]; + [context moveTo:c_head]; + [context curveTo:c_tail withCp1:c_cp1 andCp2:c_cp2]; if ([self style] != nil) { // draw edge decoration diff --git a/tikzit/src/linux/GraphRenderer.m b/tikzit/src/linux/GraphRenderer.m index c964f1b..571390f 100644 --- a/tikzit/src/linux/GraphRenderer.m +++ b/tikzit/src/linux/GraphRenderer.m @@ -140,7 +140,7 @@ void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event); if (node == nil) { return; } - NSRect nodeRect = [node boundsWithLabelOnSurface:surface]; + NSRect nodeRect = [node renderBoundsWithLabelForSurface:surface]; nodeRect = NSInsetRect (nodeRect, -2.0f, -2.0f); [surface invalidateRect:nodeRect]; } @@ -170,7 +170,7 @@ void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event); } - (BOOL) point:(NSPoint)p fuzzyHitsNode:(Node*)node { - NSRect bounds = [node boundsOnSurface:surface]; + NSRect bounds = [node renderBoundsForSurface:surface]; return NSPointInRect(p, bounds); } @@ -307,13 +307,13 @@ void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event); - (void) invalidateHalfEdge { if (halfEdgeOrigin != nil) { NSRect invRect = NSRectAroundPoints(halfEdgeEnd, halfEdgeOriginPoint); - invRect = NSUnionRect(invRect, [halfEdgeOrigin boundsWithLabelOnSurface:surface]); + invRect = NSUnionRect(invRect, [halfEdgeOrigin renderBoundsWithLabelForSurface:surface]); NSEnumerator *enumerator = [doc nodeEnumerator]; Node *node; while ((node = [enumerator nextObject]) != nil) { if ([self point:halfEdgeEnd fuzzyHitsNode:node]) { - invRect = NSUnionRect(invRect, [node boundsWithLabelOnSurface:surface]); + invRect = NSUnionRect(invRect, [node renderBoundsWithLabelForSurface:surface]); } } [surface invalidateRect:NSInsetRect (invRect, -2.0f, -2.0f)]; diff --git a/tikzit/src/linux/Node+Render.h b/tikzit/src/linux/Node+Render.h index 49667e1..60d2573 100644 --- a/tikzit/src/linux/Node+Render.h +++ b/tikzit/src/linux/Node+Render.h @@ -30,8 +30,8 @@ enum NodeState { - (Transformer*) shapeTransformerForSurface:(id)surface; // the total rendered bounds, excluding label -- (NSRect) boundsOnSurface:(id)surface; -- (NSRect) boundsWithLabelOnSurface:(id)surface; +- (NSRect) renderBoundsForSurface:(id)surface; +- (NSRect) renderBoundsWithLabelForSurface:(id)surface; - (NSString*) renderedLabel; - (NSSize) renderedLabelSizeInContext:(id)context; - (void) renderLabelToSurface:(id)surface withContext:(id)context; diff --git a/tikzit/src/linux/Node+Render.m b/tikzit/src/linux/Node+Render.m index 7450dba..907d818 100644 --- a/tikzit/src/linux/Node+Render.m +++ b/tikzit/src/linux/Node+Render.m @@ -27,16 +27,31 @@ @implementation Node (Render) +- (Shape*) shapeToRender { + if (style) { + return [Shape shapeForName:[style shapeName]]; + } else { + return [Shape shapeForName:SHAPE_CIRCLE]; + } +} + - (Transformer*) shapeTransformerForSurface:(id)surface { return [self shapeTransformerFromTransformer:[surface transformer]]; } -- (NSRect) boundsOnSurface:(id)surface { - return [self boundsUsingShapeTransform:[self shapeTransformerForSurface:surface]]; +- (NSRect) renderBoundsUsingShapeTransform:(Transformer*)shapeTrans { + float strokeThickness = style ? [style strokeThickness] : [NodeStyle defaultStrokeThickness]; + NSRect screenBounds = [shapeTrans rectToScreen:[[self shapeToRender] boundingRect]]; + screenBounds = NSInsetRect(screenBounds, -strokeThickness, -strokeThickness); + return screenBounds; +} + +- (NSRect) renderBoundsForSurface:(id)surface { + return [self renderBoundsUsingShapeTransform:[self shapeTransformerForSurface:surface]]; } -- (NSRect) boundsWithLabelOnSurface:(id)surface { - NSRect nodeBounds = [self boundsOnSurface:surface]; +- (NSRect) renderBoundsWithLabelForSurface:(id)surface { + NSRect nodeBounds = [self renderBoundsForSurface:surface]; NSRect labelRect = NSZeroRect; if (![label isEqual:@""]) { id cr = [surface createRenderContext]; @@ -127,7 +142,7 @@ [context saveState]; - [[self shape] drawPathWithTransform:shapeTrans andContext:context]; + [[self shapeToRender] drawPathWithTransform:shapeTrans andContext:context]; [context setLineWidth:strokeThickness]; if (!style) { @@ -145,7 +160,7 @@ alpha = 0.25f; RColor selectionColor = MakeSolidRColor(0.61f, 0.735f, 1.0f); - [[self shape] drawPathWithTransform:shapeTrans andContext:context]; + [[self shapeToRender] drawPathWithTransform:shapeTrans andContext:context]; [context strokePathWithColor:selectionColor andFillWithColor:selectionColor usingAlpha:alpha]; } @@ -156,7 +171,7 @@ - (BOOL) hitByPoint:(NSPoint)p onSurface:(id)surface { Transformer *shapeTrans = [self shapeTransformerForSurface:surface]; - NSRect screenBounds = [self boundsUsingShapeTransform:shapeTrans]; + NSRect screenBounds = [self renderBoundsUsingShapeTransform:shapeTrans]; if (!NSPointInRect(p, screenBounds)) { return NO; } @@ -164,7 +179,7 @@ float strokeThickness = style ? [style strokeThickness] : [NodeStyle defaultStrokeThickness]; id ctx = [surface createRenderContext]; [ctx setLineWidth:strokeThickness]; - [[self shape] drawPathWithTransform:shapeTrans andContext:ctx]; + [[self shapeToRender] drawPathWithTransform:shapeTrans andContext:ctx]; return [ctx strokeIncludesPoint:p] || [ctx fillIncludesPoint:p]; } -- cgit v1.2.3