[FIX] [ENH] NewCurves-wiz

Peace Jerome peace_the_dreamer at yahoo.com
Tue Jul 20 20:37:37 UTC 2004


I've always considered smooth closed curves with one
sharp corner a bug.



This is  final draft of the first publishable version
of Newcurves.

NewCurveMorph is a subclass of CurveMorph (for now).
It uses a true closed curve cubic smoothing for closed
curves this eliminates the 'corner' of the closed
CurveMorph. NewCurves look the same regardless of
which vertice you start with or which direction they
are traversed.

In the process of writing this I considerably rewrote
the smoothing methods and refactored stuff to be much
more reusable.

Also in the process I made changes to PolygonMorph to
make them safe for one vertex polygons. Also fixed
some inaccuracies in smoothing that are hidden by the
less demanding code currently in use.

The innovative stuff has been left in New Curves so I
could publish this first. New Curves wants to be
merged completely into Polygon.
In deference to the great number of subclasses of
Polygon it appears separately first. 

What I have here is solid and very usable. It tests
out AOK when loaded into a fresh image.

I tried to make a package of it but was stymied by the
changes I made to PolygonMorph. Those are fixes or
general enhancements so they belong there. So I have
settled for this change set inorder to get these
enhancements/fixes befor other eyes.


The gzipped and text version of the changesets are
attached.



	
		
__________________________________
Do you Yahoo!?
Vote for the stars of Yahoo!'s next ad campaign!
http://advision.webevents.yahoo.com/yahoo/votelifeengine/
--0-1899116375-1090355857=:77376
Content-Type: application/x-gzip; name="NewCurves-wiz.7.cs.gz"
Content-Transfer-Encoding: base64
Content-Description: NewCurves-wiz.7.cs.gz
Content-Disposition: attachment; filename="NewCurves-wiz.7.cs.gz"

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
9+2+HDFs9AfaRUzjy2Q1O69uotq3gfR/fc9Vlzb764V6iBBj6O2x704GWxxq
rtWbc8KSotNHVEVQaHbhPWK/7A+QazJYfXbXdwR6IZBkr60SATqDWNyjYr6f
jHq7C0hsZZJcEJoV003TwRcX1Wn1n7VD5wJFfcHpjwcNF2LSvzc84X3AXY5c
yQdG7RcF7okjaGhKbiQwLpbw8/WMyHXvziFHwse5uykICi87E165FPlUCOnV
DWdDkjux4qmmR4rtBQA2f26PGWWxWvD9YDa3t7tZgdN1r8Wv1mM3G98nQPjq
dA6+rQPLZFNd8iZDMrWONJ6wuqmhdaSV61s0rpTWV6Gi+/nNZGNOEal09dRd
lyBZ9Pn0H6frZX2k1mkje2qVja6Ri65WSRKUiiNcHDU1U1Lo7NB9Yw/3gTnG
8ZyOVen5rVLkgcsOiUq2gN8kxs1S3xvGX4meshV24z7Cn8TTkxzJbz8Wppo/
gKrmw3B18m9G08kfxNDJ9chZodrhToSFuty24SU7EPTOe7By8tcgZC3QmRW8
LcsS25qsLPtpv5GOP1fEU7Z+N1MdYRnJ8Rw2FYxu8dsQOOg8jIXB20gSSbvh
E1bbnOukC+tmyVaqcqEATepsh1BdLSM7KXLdrm5sRFfLJItdA1adqy5YqKM5
Nf3E3kxWZSCXbp2TuEvInek1RPb6GJvJmxrgqI10Y++K4Kup9JpH26k0iP0d
znwPryJVPC9tMlKl8iqvt2QzlotpZJT1/ODryLtiqH/3ePuKpFRz2tpM63rH
Dd9/sIzSgpaNG/5n4a60reVq5zzrNgqxfvUK32RLP+BRMcr/i3pt65Rn7dq7
XY9wObBwGx89DAaHwdHheHB4Mu7LNHFfM24vQgxodfuQ3Pbm0rUG4SXZmcFM
v67lBji+Coz5ocseLldt8YhUiwWPrK46YsV+LeYieJneNiK3QEmWfC80x7Ea
akTYoF4yx9Y/9uLSQ60mIUBYQwWE9Ragp9o1aXqDCd+AjEBX5IdFVhJ7qaLe
yUPLYy+1sXd+lO5yG2bHUQxYu45pWSeRnRtA427iAJZJ8vivBON4fFSC60bz
+X8Jh5bbGQVv7PDBrL+vRU0ox15XqcTrRovvgpGqPYuMVHbtpSjXhr3kVz8L
dyWmaS/8Ey71yJVu25BTYcO22GmDr1UVJIXc0HYMhixO0ts00d9ROygvUH4o
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
E0zlbI8TGbvlTMq3YseSOKOxWtQtPmOUSpCdqgRU1fr5yzs6FznWp0/1wzV8
2o/+XxWUXyobCA2G5rB5JFOG7/aQOzoe/HEmza5ox5pE0OBh2Xb8IPvGCgCy
Go8RK+wnBajUQDkKTmsRFZWdUMVCOdOQty1BDqovoTVVEZ15R4SSRuepZlmE
a/obPlZjexXe9mem1Ah/3u4CWmcs21oiCpj+1hpD4+IZWNSt7NaHN1/fGnZ3
x1fRYq/4wH7AJ4isPSRBCriEIeDjDFYouQPIemp+zaELfEO1GD2wt+zBIxy5
D/jKPamrxnkKT6gMizfh8mAt16xRq6/n8WSuNzeAP8dyB/cyvooS5g26KO2W
9IEcMm2Ol3kmM7AIqBPipWD3iyRr02XUJEfWiBPegJhykjjImguWmnoZCtYB
G7pYSX6E5AB3mSY2n5F5HBWc4EDQRc5TyO0bYE4RDiIR1cWFPanDrmCQfQYV
fE1aNCCn/AKs2N0eHhBwypK4HKsDee1sj4XCiUYN3QAFdpHugMKfROD6GSZY
dnZM6atcgnndoKDx6IOxI2GMEXxv711EnI1WVKZ0B7UxIq4ZSc5NsECAYZSQ
RcQs3SIx8XH707JbHxAYV68q8rXp+OO644+o2wjT5SnZ3FJbi8Cy6hWOndTX
QnvywO918ef5h2dYQx2y/EqJ0DKPmLUfIXFNcwemKHioZev8kJ0ipgIUQJ/2
dpy00No4LEHM6cX01Trj21Dk9YP6LQM/a2c1CNbHjLNEZbR0UwDA6d3ietyO
3Gp9fASvQfjkngdhydL43Mhg8a9ZNNZ8F5pjSV7yUTikZoPISUHv4zgNCLuK
KMgnc9VRgG7vZH9yOyOIdg9AckEcXNcRHfJoGMIemvukZmO2dxFHt9vedYF4
Y9qS5LGauq6St04V3u/E/O37b2rdKCX9yT7+/OJXR804q7ngFC3htwg7YKYN
h4kF8zhi9nzU6/9pvmrNnL6ExtVWyfN5BU5MG59S/Rg4lldpXXizd5SsLdIV
ONUPR2dPqHww4VhsiYdWVSDXhDyqDoiW7oc8FmyQQqof6SnhPlLFeRx5WW2Y
2nuBPCZ9dO2ZBbnF1dXdYdnGW2kpvIZ9/GCMp7b0bilN6CADWypL1/K8eNcd
D8W6amY70r6yfBak/LZTpQ1mH3Js89/VWGOXylVutH2SGXeIXu8Ct38iZZ96
mKVRCIfnr6tsiNKcfC/ndh+Q7fE1PIpzfEBOaXVq8GkCjhfMptN9gsZT3M+S
V93Sq9rDhUgA/135HbWCFJH6A6QeJ2LAYYs6RYBtwm49SSRidABRiNFEpHFy
2BYGyn747/gsBc9hERAGITwXzkh7lqDYV0cpPCqzCRnAyYR6lXuWCQKLLFwl
sE/I3uNWxmT0LQjaSw12J4A8MF9/DdvwWVC8xRXam8dx+WL6LJjRcq1CucIL
aEVfpAi18QipC+jvKdmhOf86lwgz9+t4gN8v5CMfX65+ubb51Q+gR2hm1K/9
6/VPat0aX9KMe5fltD4OWqcwWCJzR66XMRDcLzgvqn5gP3d4RrYiQd77WpzO
A9SBd0Nu2H4xPYuWcJ3bXC30V1uln99GV2fxLPau4QUK1BqnFz8SODDCJSGb
jq+4KIF52No6JcUMevDWqwvGWPpAWOTGJYXo5essD5EusCSWjAjY4TgAww+Z
VHgVH69wtoleYYT2YU7mLf3/PtHOUw6zTQwP60WqDdSecDHNd0EYX+n7l0FI
+MnZ9Pku2+13XiMyVK2e65+M8Ew+ACJyUhjFFzHHC+6zMiswzb3b4qhl3sGw
2vsjQX+X9RWeKIyI5/oId5Xwr1Mm8UifzuLLIVnj+vQ0m8WTYbYcJnrprbwt
BCv54ZlsAGy8Wt/Jd8C9GCItAHNd/XixGpdYpiFnNYbBj78YF8+bp1UMSYwM
CSeZ8EhGzBdB/pYmzr+fxGPjXhK1E925Isya4gRAwsUt0SQKIWJwmphhKUFo
VurhlROJh04W3pDrodsmDrz3MitKSZYwbJ9lpnZsXE6NG85TB3cjx3CJLBxH
SEiWyfl4yeyrZ4ix81EGSYbsCdfyftODB6Pd/t+dtzDrPa4AAA==

--0-1899116375-1090355857=:77376
Content-Type: text/plain; name="NewCurves-wiz.7.cs"
Content-Description: NewCurves-wiz.7.cs
Content-Disposition: inline; filename="NewCurves-wiz.7.cs"

'From Squeak3.7beta of ''1 April 2004'' [latest update: #5967] on 20 July 2004 at 2:49:54 pm'!
"Change Set:		NewCurves-wiz
Date:			18 July 2004
Author:			Jerome Peace (wiz)

This is  final draft of the first publishable version of Newcurves.

NewCurveMorph is a subclass of CurveMorph (for now). It uses a true closed curve cubic smoothing for closed curves this eliminates the 'corner' of the closed CurveMorph. NewCurves look the same regardless of which vertice you start with or which direction they are traversed.

In the process of writing this I considerably rewrote the smoothing methods and refactored stuff to be much more reusable.

Also in the process I made changes to PolygonMorph to make them safe for one vertex polygons. Also fixed some inaccuracies in smoothing that are hidden by the less demanding code currently in use.

The innovative stuff has been left in New Curves so I could publish this first. New Curves wants to be merged completely into Polygon.
In deference to the great number of subclasses of Polygon it appears separately first. 

What I have here is solid and very usable. It tests out AOK when loaded into a fresh image.

I tried to make a package of it but was stymied by the changes I made to PolygonMorph. Those are fixes or general enhancements so they belong there. So I have settled for this change set inorder to get these enhancements/fixes befor other eyes.



wiz 7/19/2004 00:30 Version 5. Fixed name of pref flag in last version. Fixed morph name for flap registry. new curves appear in object catalog now. They don't yet appear in Supplies flap.

Version 6. Postamble now set to initialize NewCurveMorph class."!


!PolygonMorph commentStamp: '<historical>' prior: 0!
This class combines the old Polygon and Curve classes.

The 1-bit fillForm to make display and containment tests reasonably fast.  However, this functionality is in the process of being supplanted by balloon capabilities, which should eventually provide anti-aliasing as well.

wiz 7/18/2004 21:26
s have made some changes to this class to

1) correct some bugs associated with one vertex polygons.

2) prepare for some enhancements with new curves.

3) add shaping items to menu.!

CurveMorph subclass: #NewCurveMorph
	instanceVariableNames: ''
	classVariableNames: 'SlopeConstantsCache'
	poolDictionaries: ''
	category: 'newCurve-Morphic-Basic'!

!NewCurveMorph commentStamp: '<historical>' prior: 0!
I want to be merged into PolygonMorph.
I implement Closed Cubic Curves and restructured routines to ease maintence and development.



New way to calculate curves.

cVariables
SlopeConstantsCache anArray size 2  indexed by nVerts \\2 .
		Each element is an array of integers. The integers represent the constants for
 		calculating slopes for closed cubic curves from the vertices. 




Class Variable SlopeConstantsCache holds a pair of arrays for even and odd number of vertices( aka knots).
Each array holds a series of constants in Integer form.
This allows slopes to be calculated directly from the array of knots.
Wonderfully it turns out that only two arrays are needed.
By matching up the knots equidistant from the point in question;
Summing the weighted differences of the pairs the unscaled slope can be arrived at.
The scale needed to get the slopes needed is trice the reciprical of the next integer in the series.
We leave the division til last to get the full benifit of the integer arithmetic.

Rounding the vertices before calculation is recommended.


Instead of calculating the number of curve subsegments in lineSegDo we add a ninth array to curve state to allow the number to be precalculated.
Getting better looking curves motivates finding a better way of guessing n. So this provides a framework for trying.

For the first pass we just used the constant 12 for every thing.
In the second pass we judge the number of segments by starting with two and doubling the number until the distance of the curve no longer increases.
Then we hone in using a binary search to find the smallest number of segments with that same curve length.


We have changed some assumptions. Previously curves were figured by solving for the second derivative  first and using the results to determine the slope and the third derivative. So lineSegDo counted on the last second deriv being a number it could use in its calculation of the number of subsegments.

Currently we just solve for slopes and the second and third derivs are derived from that. 
Also the derivation for the second and third derivs only assume C(1) (first derivitive continuity). The calculations for the slopes are the only calcs using C(2) continuity. Therefore the slopes can alternately be chosen to fit some other chriteria  and the resulting curves will still be smooth to the first degree.
A useful variant of closed slopes is to scale them by a constant.


Also the last of each element of curvestate always reflects a closing segment. And we don't add an extra row for closed curves anymore. 
That is now lineSegDo's responsibility to be aware of as it was already doing with segmented curves. So the last n does not track its old value.

Preferences:
A Preference has been added to toggle between the old (ugly) closed curves based on natural cubic slopes and the new smooth algorythim. This doesn't make much difference while newcurves are a subclass of polygons but the ambition is for newcurves to supercede polygons. This will allow backwards  compatibility.

Shapes: With closed curves a smooth oval results from rectagular or diamond vertices. So two menuitems have been added (to PolygonMorph) that allow the vertices to be set to these shapes using the current bounds of the polygon. The former state of vertices will be lost but it seems useful to lose a complicated shape and start fresh with a simple symmetrical one. 

Furthur on: Modify curveState to only contain slope and higher deriv information. Let the information about the knots only be held only in the vertices of the polygon. Once that is done curvestate will not have to be recalcutaled each time the polygon is moved but only when its shape changes.

There is also some possible speed up to be had by refining or experimenting with other number of segment calculating schemes but not as much as preserving curvestate over a move.

Furthur furthur on: Figure out how to combine straight and curved segments into a single shape in a pleasing way.

 







!

Array variableSubclass: #Cubic
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Collections-Arrayed'!

!Cubic commentStamp: 'wiz 6/17/2004 20:31' prior: 0!
I am a segment between to points. In the form of a cubic polynomial that can be evaluated between 0..1 to obtain the end points and intermediate values.
!


!Object methodsFor: '*newCurve-tests' stamp: 'wiz 7/15/2004 14:06'!
isNonZero
"Return true for numbers not zero and false for all other objects"
^ self isNumber and: [self isZero not]! !


!Integer methodsFor: '*newCurve-support' stamp: 'wiz 7/18/2004 17:00'!
cacheForClosedCubicSolution
	"Return a pair of arrays representing the integer series of  
	weights for calculating closed cubic slopes.  
	The series will be at least long enough to calculate slopes for the  
	receivers number of vertices.  
	The first of the two arrays is for even vertices the second for odd."
	"| n |  
	^ ((n := 100  
	max: (self odd  
	ifTrue: [self + 5]  
	ifFalse: [self + 6])) to: n + 1)  
	collect: [:i | i calcClosedCubicSolution first reverse]"
	"For simplicity calculate the cache w/o using half arrays this time."
	"zed is a constant"
	| n evenWeights oddWeights zed minN |
	zed := #(0 ).
	"minN is a constant"
	minN := 18.
	"A cache for n=18 will be plenty for most situations. The first 16 weights 
	will be small integers the rest large. n=18 generates the first 16 odd 
	and  even weights."
	n := minN max: self // 2 + 6.
	evenWeights := Array new: n withAll: 0.
	evenWeights at: 1 put: 1.
	(2 to: n - 2)
		do: [:i | 
			evenWeights at: i incrementBy: (evenWeights at: i - 1)
					* 4 negated.
			evenWeights at: i + 1 incrementBy: (evenWeights at: i - 1) negated].
	oddWeights := evenWeights + (zed , evenWeights allButLast).
	^ {evenWeights allButLast: 2. oddWeights allButLast: 2}! !


!PolygonMorph methodsFor: 'access' stamp: 'wiz 6/24/2004 22:50'!
midVertices
	"Return and array of midpoints for this line or closed curve"
	| midPts nextVertIx tweens |
	vertices size < 2
		ifTrue: [^ vertices].
	midPts := OrderedCollection new.
	nextVertIx := 2.
	tweens := OrderedCollection new.
	tweens add: vertices first asIntegerPoint.
	"guarantee at least two points."
	self
		lineSegmentsDo: [:p1 :p2 | 
			tweens addLast: p2 asIntegerPoint.
			p2
					= (vertices atWrap: nextVertIx)
				ifTrue: ["Found endPoint."
					midPts addLast: (tweens atWrap: tweens size + 1 // 2)
							+ (tweens at: tweens size // 2 + 1) // 2.
					"wiz 6/19/2004 20:11 adjusted to handle  
					one segment properly"
					tweens := OrderedCollection new.
					tweens add: p2 asIntegerPoint.
					"guarantee at least two points."
					nextVertIx := nextVertIx + 1]].
	^ midPts asArray! !

!PolygonMorph methodsFor: 'debug and other' stamp: 'wiz 5/1/2004 00:21'!
rotateTestFlip: aBool 
	"Return one copy of me for each vertex using each vertex as  
	the  
	starting point.  
	Vary to border color to destinguish the copies.  
	This tests closed curves for their consistency.  
	The flip boolean tests the reversed rotations."
	| len colors verts flip |
	verts := self vertices.
	flip := aBool == true
				ifTrue: [1]
				ifFalse: [0].
	len := verts size.
	colors := Color wheel: len*2 .
	(1 to: len)
		do: [:i | | j | (self copy
				borderColor: (colors at: (j:=i * 2 - flip));
				 yourself)
				setVertices: (verts flipRotated: j);
				 openInWorld]! !

!PolygonMorph methodsFor: 'drawing' stamp: 'wiz 6/22/2004 16:06'!
drawArrowsOn: aCanvas 
	"Answer (possibly modified) endpoints for border drawing"
	"ArrowForms are computed only upon demand"
	| array |
	self hasArrows
		ifFalse: [^ self].
	"Nothing to do"
	borderColor isColor
		ifFalse: [^ self].
	array := Array new: 2.
	"Prevent crashes for #raised or #inset borders"
	array
		at: 2
		put: ((arrows == #forward
					or: [arrows == #both])
				ifTrue: [self
						drawArrowOn: aCanvas
						at: vertices last
						from: self nextToLastPoint]
				ifFalse: [vertices last]).
	array
		at: 1
		put: ((arrows == #back
					or: [arrows == #both])
				ifTrue: [self
						drawArrowOn: aCanvas
						at: vertices first
						from: self nextToFirstPoint]
				ifFalse: [vertices first]).
	^ array! !

!PolygonMorph methodsFor: 'drawing' stamp: 'wiz 6/22/2004 15:56'!
drawBorderOn: aCanvas usingEnds: anArray 
	"Display my border on the canvas."
	"NOTE: Much of this code is also copied in  
	drawDashedBorderOn:  
	(should be factored)"
	| bigClipRect p1i p2i style |
	borderDashSpec
		ifNotNil: [^ self drawDashedBorderOn: aCanvas usingEnds: anArray].
	style := self borderStyle.
	bigClipRect := aCanvas clipRect expandBy: self borderWidth + 1 // 2.
	self
		lineSegmentsDo: [:p1 :p2 | 
			p1i := p1 asIntegerPoint.
			p2i := p2 asIntegerPoint.
			self hasArrows
				ifTrue: ["Shorten line ends so as not to interfere with tip  
					of arrow."
					((arrows == #back
								or: [arrows == #both])
							and: [p1 = vertices first])
						ifTrue: [p1i := anArray first asIntegerPoint].
					((arrows == #forward
								or: [arrows == #both])
							and: [p2 = vertices last])
						ifTrue: [p2i := anArray last asIntegerPoint]].
			(closed
					or: ["bigClipRect intersects: (p1i rect: p2i)  
						optimized:"
						((p1i min: p2i)
							max: bigClipRect origin)
							<= ((p1i max: p2i)
									min: bigClipRect corner)])
				ifTrue: [style
						drawLineFrom: p1i
						to: p2i
						on: aCanvas]]! !

!PolygonMorph methodsFor: 'drawing' stamp: 'wiz 6/22/2004 15:56'!
drawDashedBorderOn: aCanvas usingEnds: anArray 
	"Display my border on the canvas. NOTE: mostly copied from  
	drawBorderOn:"
	| lineColor bevel topLeftColor bottomRightColor bigClipRect p1i p2i segmentOffset |
	(borderColor isNil
			or: [borderColor isColor
					and: [borderColor isTransparent]])
		ifTrue: [^ self].
	lineColor := borderColor.
	bevel := false.
	"Border colors for bevelled effects depend on CW ordering of  
	vertices"
	borderColor == #raised
		ifTrue: [topLeftColor := color lighter.
			bottomRightColor := color darker.
			bevel := true].
	borderColor == #inset
		ifTrue: [topLeftColor := owner colorForInsets darker.
			bottomRightColor := owner colorForInsets lighter.
			bevel := true].
	bigClipRect := aCanvas clipRect expandBy: self borderWidth + 1 // 2.
	segmentOffset := self borderDashOffset.
	self
		lineSegmentsDo: [:p1 :p2 | 
			p1i := p1 asIntegerPoint.
			p2i := p2 asIntegerPoint.
			self hasArrows
				ifTrue: ["Shorten line ends so as not to interfere with tip  
					of arrow."
					((arrows == #back
								or: [arrows == #both])
							and: [p1 = vertices first])
						ifTrue: [p1i := anArray first asIntegerPoint].
					((arrows == #forward
								or: [arrows == #both])
							and: [p2 = vertices last])
						ifTrue: [p2i := anArray last asIntegerPoint]].
			(closed
					or: ["bigClipRect intersects: (p1i rect: p2i)  
						optimized:"
						((p1i min: p2i)
							max: bigClipRect origin)
							<= ((p1i max: p2i)
									min: bigClipRect corner)])
				ifTrue: [bevel
						ifTrue: [lineColor := (p1i quadrantOf: p2i)
											> 2
										ifTrue: [topLeftColor]
										ifFalse: [bottomRightColor]].
					segmentOffset := aCanvas
								line: p1i
								to: p2i
								width: borderWidth
								color: lineColor
								dashLength: borderDashSpec first
								secondColor: borderDashSpec third
								secondDashLength: borderDashSpec second
								startingOffset: segmentOffset]]! !

!PolygonMorph methodsFor: 'editing' stamp: 'wiz 7/18/2004 23:01'!
addHandles
	"Put moving handles at the vertices. Put adding handles at edge 
	midpoints. Moving over adjacent vertex and dropping will delete a 
	vertex. "
	| handle newVert tri |
	self removeHandles.
	handles := OrderedCollection new.
	tri := Array
				with: 0 @ -4
				with: 4 @ 3
				with: -3 @ 3.
	vertices
		withIndexDo: [:vertPt :vertIndex | 
			handle := EllipseMorph
						newBounds: (Rectangle center: vertPt extent: 8 @ 8)
						color: Color yellow.
			handle
				on: #mouseMove
				send: #dragVertex:event:fromHandle:
				to: self
				withValue: vertIndex.
			handle
				on: #mouseUp
				send: #dropVertex:event:fromHandle:
				to: self
				withValue: vertIndex.
			self addMorph: handle.
			handles addLast: handle.
			(closed
					or: [1 = vertices size
						"Give a small polygon a chance to grow. -wiz"]
					or: [vertIndex < vertices size])
				ifTrue: [newVert := PolygonMorph
								vertices: (tri
										collect: [:p | p + (vertPt
													+ (vertices atWrap: vertIndex + 1) // 2)])
								color: Color green
								borderWidth: 1
								borderColor: Color black.
					newVert
						on: #mouseDown
						send: #newVertex:event:fromHandle:
						to: self
						withValue: vertIndex.
					self addMorph: newVert.
					handles addLast: newVert]].
	smoothCurve
		ifTrue: [self updateHandles; layoutChanged].
	self changed! !

!PolygonMorph methodsFor: 'editing' stamp: 'wiz 7/18/2004 23:08'!
dropVertex: ix event: evt fromHandle: handle
	"Leave vertex in new position. If dropped ontop another vertex delete this one.
	Check for too few vertices before deleting. The alternative 
				is not pretty -wiz"
	| p |
	p := vertices at: ix.
	(vertices size >= 2
			and: ["check for too few vertices before deleting. The alternative 
				is not pretty -wiz"
				((vertices atWrap: ix - 1)
						dist: p)
						< 3
					or: [((vertices atWrap: ix + 1)
							dist: p)
							< 3]])
		ifTrue: ["Drag a vertex onto its neighbor means delete"
			self
				setVertices: (vertices
						copyReplaceFrom: ix
						to: ix
						with: Array new)].
	evt shiftPressed
		ifTrue: [self removeHandles]
		ifFalse: [self addHandles
			"remove then add to recreate"]! !

!PolygonMorph methodsFor: 'editing' stamp: 'wiz 6/24/2004 23:03'!
updateHandles
	| newVert oldVert |
	self isCurvy
		ifTrue: [handles first center: vertices first.
			handles last center: vertices last.
			self midVertices
				withIndexDo: [:midPt :vertIndex | (closed
							or: [vertIndex < vertices size])
						ifTrue: [newVert := handles atWrap: vertIndex * 2.
							newVert position: midPt - (newVert extent // 2)]]]
		ifFalse: [vertices
				withIndexDo: [:vertPt :vertIndex | 
					oldVert := handles at: vertIndex * 2 - 1.
					oldVert position: vertPt - (oldVert extent // 2).
					(closed
							or: [vertIndex < vertices size])
						ifTrue: [newVert := handles at: vertIndex * 2.
							newVert position: vertPt
									+ (vertices atWrap: vertIndex + 1) - newVert extent // 2 + (1 @ -1)]]]! !

!PolygonMorph methodsFor: 'menu' stamp: 'wiz 7/18/2004 17:58'!
addCustomMenuItems: aMenu hand: aHandMorph 
	| lineName |
	super addCustomMenuItems: aMenu hand: aHandMorph.
	aMenu
		addUpdating: #handlesShowingPhrase
		target: self
		action: #showOrHideHandles.
	vertices size > 2
		ifTrue: [aMenu
				addUpdating: #openOrClosePhrase
				target: self
				action: #makeOpenOrClosed.
			lineName := (closed
						ifTrue: ['outline']
						ifFalse: ['line']) translated.
			self isCurve
				ifTrue: [aMenu
						add: ('make segmented {1}' translated format: {lineName})
						action: #toggleSmoothing]
				ifFalse: [aMenu
						add: ('make smooth {1}' translated format: {lineName})
						action: #toggleSmoothing]].
	aMenu add: 'specify dashed line' translated action: #specifyDashedLine.
	"aMenu add: 'use debug border' translated action: #showSegmentsBorderStyle."
	self isOpen
		ifTrue: [aMenu addLine.
			aMenu
				addWithLabel: '---'
				enablement: [self isOpen
						and: [arrows ~~ #none]]
				action: #makeNoArrows.
			aMenu
				addWithLabel: '-->'
				enablement: [self isOpen
						and: [arrows ~~ #forward]]
				action: #makeForwardArrow.
			aMenu
				addWithLabel: '<--'
				enablement: [self isOpen
						and: [arrows ~~ #back]]
				action: #makeBackArrow.
			aMenu
				addWithLabel: '<->'
				enablement: [self isOpen
						and: [arrows ~~ #both]]
				action: #makeBothArrows.
			aMenu add: 'customize arrows' translated action: #customizeArrows:.
			(self hasProperty: #arrowSpec)
				ifTrue: [aMenu add: 'standard arrows' translated action: #standardArrows]]
			ifFalse: [aMenu addLine.
			aMenu
				addWithLabel: 'make inscibed diamondOval'
				enablement: [self isClosed ]
				action: #diamondOval.
			aMenu
				addWithLabel: 'make enclosing rectangleOval'
				enablement: [self isClosed ]
					action: #rectOval.
					]! !

!PolygonMorph methodsFor: 'testing' stamp: 'wiz 7/18/2004 23:00'!
hasArrows
	"Are all the conditions meet for having arrows?"
	^ (closed
		or: [arrows == #none
				or: [vertices size < 2]]) not! !

!PolygonMorph methodsFor: 'testing' stamp: 'wiz 5/2/2004 22:03'!
isCurvy
	"Test for significant curves.  
	Small smoothcurves in practice are straight."
	^ smoothCurve
		and: [vertices size > 2]! !

!PolygonMorph methodsFor: 'private' stamp: 'wiz 6/22/2004 15:54'!
arrowForms
	"ArrowForms are computed only upon demand"
	arrowForms
		ifNotNil: [^ arrowForms].
	arrowForms := Array new.
	self hasArrows
		ifFalse: [^ arrowForms].
	(arrows == #forward
			or: [arrows == #both])
		ifTrue: [arrowForms := arrowForms
						copyWith: (self computeArrowFormAt: vertices last from: self nextToLastPoint)].
	(arrows == #back
			or: [arrows == #both])
		ifTrue: [arrowForms := arrowForms
						copyWith: (self computeArrowFormAt: vertices first from: self nextToFirstPoint)].
	^ arrowForms! !

!PolygonMorph methodsFor: 'shaping' stamp: 'wiz 7/18/2004 23:05'!
diamondOval
	"Retrun an array of edge midpoint vertices. 
	Order of vertices is in the tradion of warpblt quads."
	| b r |
	b := self bounds.
	r := {b leftCenter. b bottomCenter. b rightCenter. b topCenter}.
	self setVertices: r! !

!PolygonMorph methodsFor: 'shaping' stamp: 'wiz 7/18/2004 23:03'!
rectOval
	"Retrun an array of corner vertices.
	Order of vertices is in the tradion of warpblt quads."
	| b r |
	b := self bounds.
	r := {b topLeft. b bottomLeft. b bottomRight. b topRight}.
	self setVertices: r! !


!NewCurveMorph methodsFor: 'testing' stamp: 'wiz 7/18/2004 22:53'!
hasArrows
"Are all the conditions meet for having arrows?"
	^ (closed
		or: [arrows == #none
				or: [vertices size < 2]]) not! !

!NewCurveMorph methodsFor: 'testing' stamp: 'wiz 5/2/2004 22:02'!
isCurvy
"Test for significant curves. 
Small smoothcurves in practice are straight."

^(smoothCurve
			and: [vertices size > 2]) ! !

!NewCurveMorph methodsFor: 'editing' stamp: 'wiz 7/18/2004 22:56'!
addHandles
	"Put moving handles at the vertices. Put adding handles at edge midpoints.
	Moving over adjacent vertex and dropping will delete a vertex." 
	| handle newVert tri |
	self removeHandles.
	handles := OrderedCollection new.
	tri := Array
				with: 0 @ -4
				with: 4 @ 3
				with: -3 @ 3.
	vertices
		withIndexDo: [:vertPt :vertIndex | 
			handle := EllipseMorph
						newBounds: (Rectangle center: vertPt extent: 8 @ 8)
						color: Color yellow.
			handle
				on: #mouseMove
				send: #dragVertex:event:fromHandle:
				to: self
				withValue: vertIndex.
			handle
				on: #mouseUp
				send: #dropVertex:event:fromHandle:
				to: self
				withValue: vertIndex.
			self addMorph: handle.
			handles addLast: handle.
			(closed
					or: [1 = vertices size
						"Give a small polygon a chance to grow.  
						-wiz"]
					or: [vertIndex < vertices size])
				ifTrue: [newVert := PolygonMorph
								vertices: (tri
										collect: [:p | p + (vertPt
													+ (vertices atWrap: vertIndex + 1) // 2)])
								color: Color green
								borderWidth: 1
								borderColor: Color black.
					newVert
						on: #mouseDown
						send: #newVertex:event:fromHandle:
						to: self
						withValue: vertIndex.
					self addMorph: newVert.
					handles addLast: newVert]].
	self isCurvy
		ifTrue: [self updateHandles; layoutChanged].
	self changed! !

!NewCurveMorph methodsFor: 'private' stamp: 'wiz 7/18/2004 22:57'!
curveBounds
	| curveBounds pointAfterFirst pointBeforeLast |
	
	"Compute the bounds from actual curve traversal, with  
	leeway for borderWidth.  
	Also note the next-to-first and next-to-last points for arrow  
	directions."
	self isCurvy
		ifFalse: [^ (Rectangle encompassing: vertices)
				expandBy: borderWidth + 1 // 2].
	curveState := nil.
	"Force recomputation"
	curveBounds := vertices first corner: vertices last.
	pointAfterFirst := nil.
	self
		lineSegmentsDo: [:p1 :p2 | 
			pointAfterFirst isNil
				ifTrue: [pointAfterFirst := p2 asIntegerPoint.
					curveBounds := curveBounds encompass: p1 asIntegerPoint].
			curveBounds := curveBounds encompass: p2 asIntegerPoint.
			pointBeforeLast := p1 asIntegerPoint].
	curveState at: 2 put: pointAfterFirst.
	curveState at: 3 put: pointBeforeLast.
	^ curveBounds expandBy: borderWidth + 1 // 2! !

!NewCurveMorph methodsFor: 'smoothing' stamp: 'wiz 7/18/2004 23:53'!
coefficients
	"Compute an array for the coefficients."
	| verts vertXs vertYs slopeXs slopeYs coefficients |
	curveState
		ifNotNil: [^ curveState at: 1].
	verts := self vertices.
	verts size < 1
		ifTrue: [^ self].
	"Less than three points handled as segments by our  
	lineSegmentsDo:"
	(Preferences valueOfFlag: #newCurves)
		ifFalse: [closed
				ifTrue: [verts := verts , verts first asOrderedCollection]].
	coefficients := {vertXs := verts
						collect: [:p | p x asFloat]. slopeXs := self slopes: vertXs. vertXs changeInSlopes: slopeXs. vertXs changeOfChangesInSlopes: slopeXs. vertYs := verts
						collect: [:p | p y asFloat]. slopeYs := self slopes: vertYs. vertYs changeInSlopes: slopeYs. vertYs changeOfChangesInSlopes: slopeYs. Array new: verts size withAll: 12}.
	coefficients
		at: 9
		put: ((1 to: verts size)
				collect: [:i | (coefficients cubicPointPolynomialAt: i) bestSegments]).
	(Preferences valueOfFlag: #newCurves)
		ifFalse: [closed
				ifTrue: [coefficients := coefficients
								collect: [:each | each allButLast]]].
	curveState := {coefficients. nil. nil}.
	self computeNextToEndPoints.
	^ coefficients! !

!NewCurveMorph methodsFor: 'smoothing' stamp: 'wiz 6/19/2004 23:36'!
lineSegmentsDo: endPointsBlock 
	"Emit a sequence of segment endpoints into endPointsBlock."
	"Unlike the method this one overrides we expect the curve  
	coefficents not the dirivatives"
	"Also unlike the overiden method the smooth closed curve does 
	not need an extra vertex.  
	We take care of the extra endpoint here. Just like for  
	segmented curves."
	| n t x y x1 x2 x3 y1 y2 y3 beginPoint endPoint cs |
	vertices size < 1
		ifTrue: [^ self].
	"test too few vertices first"
	self isCurvy
		ifFalse: [beginPoint := nil.
			"smoothCurve  
			ifTrue: [cs := self coefficients]."
			"some things still depend on smoothCurves having  
			curveState"
			vertices
				do: [:vert | 
					beginPoint
						ifNotNil: [endPointsBlock value: beginPoint value: vert].
					beginPoint := vert].
			(closed
					or: [vertices size = 1])
				ifTrue: [endPointsBlock value: beginPoint value: vertices first].
			^ self].
	"For curves we include all the interpolated sub segments."
	"self assert: [(vertices size > 2 )].	"
	cs := self coefficients.
	beginPoint := (x := cs first first) @ (y := cs fifth first).
	(closed
		ifTrue: [1 to: cs first size]
		ifFalse: [1 to: cs first size - 1])
		do: [:i | 
			"taylor series coefficients"
			x1 := cs second at: i.
			y1 := cs sixth at: i.
			x2 := cs third at: i.
			y2 := cs seventh at: i.
			x3 := cs fourth at: i.
			y3 := cs eighth at: i.
			n := cs ninth at: i.
			"guess n 
			n := 5 max: (x2 abs + y2 abs * 2.0 + (cs third atWrap: i 
			+ 1) abs + (cs seventh atWrap: i + 1) abs / 100.0) 
			rounded. "
			1
				to: n - 1
				do: [:j | 
					t := j asFloat / n asFloat.
					endPoint := x3 * t + x2 * t + x1 * t + x @ (y3 * t + y2 * t + y1 * t + y).
					endPointsBlock value: beginPoint value: endPoint.
					beginPoint := endPoint].
			endPoint := (x := cs first atWrap: i + 1) @ (y := cs fifth atWrap: i + 1).
			endPointsBlock value: beginPoint value: endPoint.
			beginPoint := endPoint]! !

!NewCurveMorph methodsFor: 'smoothing' stamp: 'wiz 5/4/2004 00:27'!
nextToFirstPoint
	"For arrow direction"
	self isCurvy
		ifTrue: [curveState
				ifNil: [self coefficients].
			^ curveState second]
		ifFalse: [^ vertices second]! !

!NewCurveMorph methodsFor: 'smoothing' stamp: 'wiz 5/4/2004 00:26'!
nextToLastPoint
	"For arrow direction"
	self isCurvy
		ifTrue: [curveState
				ifNil: [self coefficients].
			^ curveState third]
		ifFalse: [^ vertices at: vertices size - 1]! !

!NewCurveMorph methodsFor: 'smoothing' stamp: 'wiz 7/18/2004 23:49'!
slopes: knots
	"Choose slopes according to state of polygon and preferences" 
	self isCurvy
		ifFalse: [^ knots segmentedSlopes].
	^ (closed
			and: [Preferences valueOfFlag: #newCurves])
		ifTrue: [knots closedCubicSlopes]
		ifFalse: [knots naturalCubicSlopes]! !

!NewCurveMorph methodsFor: 'initialization' stamp: 'wiz 7/18/2004 22:53'!
initialize
 "We use an oval shape because we wear it well."
 
	super initialize.
	self diamondOval! !


!NewCurveMorph class methodsFor: 'accessing' stamp: 'wiz 6/20/2004 00:01'!
weightsFor: index 
	"Insure we have cached enough weights for index vertices.  
	Return the weights matching index's parity."
	| planB |
	index isNonZero
		ifFalse: [self error: 'Index must be nonzero integer'].
	planB := [SlopeConstantsCache := index cacheForClosedCubicSolution].
	(SlopeConstantsCache ifNil: planB) first size * 2 > index ifFalse: planB.
	^ SlopeConstantsCache at: index \\ 2 + 1! !

!NewCurveMorph class methodsFor: 'parts bin' stamp: 'wiz 7/18/2004 22:59'!
descriptionForPartsBin
"We are very much like curve only better looking."
	^ self
		partName: 'NewCurve'
		categories: #('Graphics' ' Basic 1 ' )
		documentation: 'A smooth wiggly curve, or a smooth curved solid.  Shift-click to get handles and move the points.'! !

!NewCurveMorph class methodsFor: 'class initialization' stamp: 'wiz 7/18/2004 20:25'!
initialize
	"NewCurveMorph initialize"

	Preferences preferenceAt: #newCurves ifAbsent: [
		Preferences addPreference: #newCurves
			category: #morphic
			default: true
			balloonHelp: 'if true, closed newCurveMorphs will be smoother and more symmetrical all about. If false they will mimic the old curve shapes with the one sharp bend.'
	].
  
		self registerInFlapsRegistry.	
	! !

!NewCurveMorph class methodsFor: 'class initialization' stamp: 'wiz 7/19/2004 00:28'!
registerInFlapsRegistry
	"Register the receiver in the system's flaps registry"
	self environment
		at: #Flaps
		ifPresent: [:cl | cl registerQuad: #(NewCurveMorph		authoringPrototype		'NewCurve'		'A curve')
						forFlapNamed: 'PlugIn Supplies'.
						cl registerQuad: #(NewCurveMorph		authoringPrototype		'NewCurve'		'A curve')
						forFlapNamed: 'Supplies'.]! !


!SequenceableCollection methodsFor: '*newCurves-cubic support' stamp: 'wiz 7/18/2004 23:12'!
asCubic
	"Convert this point array to a Cubic object"
	self
		assert: [self size = 4].
	self
		assert: [self
				allSatisfy: [:each | each isPoint]].
	^ Cubic withAll: self! !

!SequenceableCollection methodsFor: '*newCurves-cubic support' stamp: 'wiz 5/2/2004 15:47'!
changeInSlopes: slopes 
	"A message to knots of a spline. Returns an array with the 3rd cubic coeff."
	"The last nth item is correct iff this is a closed cubic.
	Presumably that is the only time we care.
	We alway return the same sized array as self."
	| n slopeChanges |
	n := self size.
	n = slopes size
		ifFalse: [^ self error: 'vertices and slopes differ in number'].
	slopeChanges := Array new: n.
	(1 to: n)
		do: [:i | slopeChanges at: i put: (self atWrap: i + 1)
					- (self at: i) * 3 - ((slopes at: i)
						* 2)
					- (slopes atWrap: i + 1)].
	
	^ slopeChanges! !

!SequenceableCollection methodsFor: '*newCurves-cubic support' stamp: 'wiz 5/2/2004 15:53'!
changeOfChangesInSlopes: slopes 
	"A message to knots of a spline. Returns an array with the 4rd 
	cubic coeff."
	"The last nth item is correct iff this is a closed cubic. 
	Presumably that is the only time we care. 
	We alway return the same sized array as self."
	| n changes |
	n := self size.
	n = slopes size
		ifFalse: [^ self error: 'vertices and slopes differ in number'].
	changes := Array new: n.
	(1 to: n)
		do: [:i | changes at: i put: (self at: i)
					- (self atWrap: i + 1) * 2
					+ (slopes at: i)
					+ (slopes atWrap: i + 1)].
	
	^ changes! !

!SequenceableCollection methodsFor: '*newCurves-cubic support' stamp: 'wiz 6/22/2004 15:25'!
closedCubicSlopes
	"Like closed cubic but returns Cubic w/o the repeated first  
	point at the end."
	"Takes a collection of numbers or points representing the knots  
	and returns the slopes."
	"Uses a method exploiting the symmetries of the closed cubic.  
	From the number of knots (vertices) alone the closed solution  
	for any slope as a function of the knots is obtained.  
	It consists of a half array and a scale.  
	For simplicity we solve for 1/3 the slope and then adjust the  
	scale to compensate.  
	Using that to calculate two adjacent slopes and then use those  
	slopes to calculate all the others from the equation  
	b0=a2-a0-b2-4b1.  
	Finally all slopes are scaled.  
	The advantages are we can work with integers until late in the  
	calculation. If we are careful to use integer points for vertices  
	only the scaling is non-integer.  
	And all the weights are precalculated and stored in pool  
	variables. So that doesn't have to take much time either.  
	And since we are working with halfarrays; half the work.  
	ehh?"
	| nVerts scale weights |
	nVerts := self size.
	weights := NewCurveMorph weightsFor: nVerts.
	scale := weights at: nVerts + 1 // 2.
	^ (self
		unscaledSlopesAt: nVerts
		with: (self unscaledClosedCubicSlopeAt: nVerts using: weights)
		nextTo: (self unscaledClosedCubicSlopeAt: 1 using: weights)
		atScale: scale)
		* (3 / scale) asFloat! !

!SequenceableCollection methodsFor: '*newCurves-cubic support' stamp: 'wiz 7/18/2004 23:18'!
cubicPointPolynomialAt: vIndex
	"From curve information assemble a 4-array of points representing the coefficents for curve segment between to points. Beginning point is first point in array endpoint is the pointSum of the array. Meant to be sent to newcurves idea of curve coefficents." 
	^ ((1 to: 4)
		collect: [:i | ((self at: i)
				at: vIndex)
				@ ((self at: 4 + i)
						at: vIndex)]) asCubic! !

!SequenceableCollection methodsFor: '*newCurves-cubic support' stamp: 'wiz 5/2/2004 14:36'!
naturalCubicSlopes
	"for a collection of floats. computes the natural cubic curve fit  
	and  
	outputs a collection of cubic polynomials. This is a direct  
	squeak  
	transliteration of the java code below."
	"The java code is from the net tutorial on splines that I found  
	recently.  
	Not my original work. need tofind right attirbution."
	" 
	public class NatCubic extends ControlCurve{  
	 
	/* calculates the natural cubic spline that interpolates  
	y[0], y[1], ... y[n]  
	The first segment is returned as  
	C[0].a + C[0].b*u + C[0].c*u^2 + C[0].d*u^3 0<=u <1  
	the other segments are in C[1], C[2], ... C[n-1] */  
	 
	Cubic[] calcNaturalCubic(int n, int[] x) {  
	float[] gamma = new float[n+1];  
	float[] delta = new float[n+1];  
	float[] D = new float[n+1];  
	int i;  
	/* We solve the equation  
	[2 1 ] [D[0]] [3(x[1] - x[0]) ]  
	|1 4 1 | |D[1]| |3(x[2] - x[0]) |  
	| 1 4 1 | | . | = | . |  
	| ..... | | . | | . |  
	| 1 4 1| | . | |3(x[n] - x[n-2])|  
	[ 1 2] [D[n]] [3(x[n] - x[n-1])]  
	 
	by using row operations to convert the matrix to upper  
	triangular  
	and then back sustitution. The D[i] are the derivatives at the  
	knots.  
	*/  
	 
	gamma[0] = 1.0f/2.0f;  
	for ( i = 1; i < n; i++) {  
	gamma[i] = 1/(4-gamma[i-1]);  
	}  
	gamma[n] = 1/(2-gamma[n-1]);  
	 
	delta[0] = 3*(x[1]-x[0])*gamma[0];  
	for ( i = 1; i < n; i++) {  
	delta[i] = (3*(x[i+1]-x[i-1])-delta[i-1])*gamma[i];  
	}  
	delta[n] = (3*(x[n]-x[n-1])-delta[n-1])*gamma[n];  
	 
	D[n] = delta[n];  
	for ( i = n-1; i >= 0; i--) {  
	D[i] = delta[i] - gamma[i]*D[i+1];  
	}  
	 
	/* now compute the coefficients of the cubics */  
	Cubic[] C = new Cubic[n];  
	for ( i = 0; i < n; i++) {  
	C[i] = new Cubic((float)x[i], D[i], 3*(x[i+1] - x[i]) - 2*D[i] -  
	D[i+1],  
	2*(x[i] - x[i+1]) + D[i] + D[i+1]);  
	}  
	return C;  
	}  
	 
	 
	final int STEPS = 12;  
	 
	/* draw a cubic spline */  
	public void paint(Graphics g){  
	super.paint(g);  
	if (pts.npoints >= 2) {  
	Cubic[] X = calcNaturalCubic(pts.npoints-1, pts.xpoints);  
	Cubic[] Y = calcNaturalCubic(pts.npoints-1, pts.ypoints);  
	 
	/* very crude technique - just break each segment up into  
	steps lines */  
	Polygon p = new Polygon();  
	p.addPoint((int) Math.round(X[0].eval(0)),  
	(int) Math.round(Y[0].eval(0)));  
	for (int i = 0; i < X.length; i++) {  
	for (int j = 1; j <= STEPS; j++) {  
	float u = j / (float) STEPS;  
	p.addPoint(Math.round(X[i].eval(u)),  
	Math.round(Y[i].eval(u)));  
	}  
	}  
	g.drawPolyline(p.xpoints, p.ypoints, p.npoints);  
	}  
	}  
	}  
	"
	"Translation note: indexes in java start at 0 in squeak at 1. So  
	java	squeak  
	0		1		first index  
	n		n1		last index			  
	n+1		n1		size	  
	"
	| x gamma delta D n1 |
	n1 := self size.
	n1 < 3
		ifTrue: [self error: 'Less than 3 points makes a poor curve'].
	x := self.
	gamma := Array new: n1.
	delta := Array new: n1.
	"C := Array new: n1."
	D := Array new: n1.
	gamma at: 1 put: 1.0 / 2.0.
	(2 to: n1 - 1)
		do: [:i | gamma at: i put: 1.0 / (4.0
						- (gamma at: i - 1))].
	gamma at: n1 put: 1.0 / (2.0
				- (gamma at: n1 - 1)).
	delta at: 1 put: 3.0 * ((x at: 2)
				- (x at: 1))
			* (gamma at: 1).
	(2 to: n1 - 1)
		do: [:i | delta at: i put: 3.0 * ((x at: i + 1)
						- (x at: i - 1))
					- (delta at: i - 1)
					* (gamma at: i)].
	delta at: n1 put: 3.0 * ((x at: n1)
				- (x at: n1 - 1))
			- (delta at: n1 - 1)
			* (gamma at: n1).
	D
		at: n1
		put: (delta at: n1).
	(1 to: n1 - 1)
		reverseDo: [:i | D at: i put: (delta at: i)
					- ((gamma at: i)
							* (D at: i + 1))].
	"debug. Get a snapshot by arming oneshot."
	" 
	self  
	doOnlyOnce: [({'gamma'} , gamma) explore.  
	({'delta'} , delta) explore.  
	({'D'} , D) explore].  
	"
	^ D" 
	(1 to: n1 - 1) 
	do: [:i | C at: i put: {x at: i. D at: i. 3 * ((x at: i + 1) 
	- (x at: i)) - (2 
	* (D at: i)) 
	- (D at: i + 1). 2 * ((x at: i) 
	- (x at: i + 1)) 
	+ (D at: i) 
	+ (D at: i + 1)}]. 
	C at: n1 put: {x at: n1. D at: n1. 0.0. (D at: n1) negated}. 
	^ C"! !

!SequenceableCollection methodsFor: '*newCurves-cubic support' stamp: 'wiz 5/2/2004 22:43'!
segmentedSlopes
	"For a collection of floats. Returns the slopes for straight 
	segments between vertices."
	"last slope closes the polygon. Always return same size as 
	self. "
	^ self
		collectWithIndex: [:x :i | (self atWrap: i + 1)
				- x]! !

!SequenceableCollection methodsFor: '*newCurves-cubic support' stamp: 'wiz 6/22/2004 15:25'!
unscaledClosedCubicSlopeAt: index using: weights 
	"returns unscaled slope at index"
	"Weights is an array of integers to scale the contributions of the  
	pairs to the slope."
	"The scale is the reciprocal of the next integer in the series times 
	3. For closed cubic splines the series is the same for all even  
	or  
	for all odd vertices.  
	This routine allows those to be cached."
	| foldedSize |
	^ ((1 to: (foldedSize := self size - 1 // 2))
		collect: [:i | (self atWrap: index - i)
				- (self atWrap: index + i)
				* (weights at: foldedSize + 1 - i)]) sum! !

!SequenceableCollection methodsFor: '*newCurves-cubic support' stamp: 'wiz 5/27/2004 23:50'!
unscaledSlopesAt: index with: middleSlope nextTo: nextSlope atScale: scale 
	"Return unscaled slopes for all knots on curve. Calculating 
	from knots  
	and two consecutive slopes."
	"From the eq b0+4b1+b2= (a2-a1)*3. The factor of 3 has been  
	left out of this routine for simplicity.  
	To scale the slopes properly they must be multiplied by 3 * the 
	reciprocal of scale. 
	This is done so integer points can be handled by integer 
	arithmetic until the last instant."
	| fullSize results |
	(fullSize := self size) < 3
		ifTrue: [self error: 'Array to small.'].
	"fullSize < index  
	ifTrue: [self inform: 'Warning!! index out of range']."
	results := self species new: fullSize.
	"withAll: 0"
	results at: index put: middleSlope.
	results atWrap: index + 1 put: nextSlope.
	(index + 2 to: index + fullSize - 1)
		do: [:i | results atWrap: i put: (results atWrap: i - 1)
					* 4 negated
					- (results atWrap: i - 2) + ((self atWrap: i)
						- (self atWrap: i - 2) * scale)].
	^ results! !

!SequenceableCollection methodsFor: '*newCurves-cubic support' stamp: 'wiz 4/17/2004 18:44'!
wizPolynomialEval: thisX 
	"This is a simplification of polynomialEval"
	"Treat myself as the coeficients of a polynomial in X. Evaluate it with  
	thisX. First element is the constant and last is the coeficient for the  
	highest power."
	"#(1 2 3) polynomialEval: 2"
	"#(1 2 3) wizPolynomialEval: 2"
	"is 3*X^2 + 2*X + 1 with X = 2"
	| sum |
	sum := 0.
	self
		reverseDo: [:term | sum := sum * thisX
						+ term].
	^ sum! !

!SequenceableCollection methodsFor: 'converting-fliprotate' stamp: 'wiz 4/7/2004 09:31'!
flipRotated: flipIndex 
	"Answer a copy of the receiver with element order indicated by  
	flipIndex."
	"Examples:"
	"'frog' flipRotated: 1"
	"[ :c | (1 to: c size * 2) collect:  
	[ :i | c flipRotated: i ]  
	] value: 'frog'."
	"Lsb of flipIndex indicates whether list is reversed"
	"The flipIndex // 2 gives how much to rotate by after reversing"
	"A good way to think of this is a piece of pie in a pie plate being flip  
	over its leading edge successively."
	"flipIndex > 2 * n are allowed to make it possible to store an array of  
	indexes in an integer."
	| n result src twist |
	n := self size.
	flipIndex \\ (n * 2) = 0
		ifTrue: [^ self].
	"allow for doing nothing"
	result := self species new: n.
	twist := flipIndex // 2 \\ n.
	src := 0.
	(flipIndex even
		ifTrue: [1 + twist to: n + twist]
		ifFalse: [n - 1 - twist to: twist negated by: -1])
		do: [:i | result
				at: (src := src + 1)
				put: (self atWrap: i)].
	^ result! !


!Cubic methodsFor: '*newCurves-cubic support' stamp: 'wiz 6/17/2004 22:32'!
bestSegments
	"Return the smallest integer number of segments that give the 
	best curve."
	^ self honeIn: self calcEnoughSegments! !

!Cubic methodsFor: '*newCurves-cubic support' stamp: 'wiz 6/18/2004 23:12'!
calcEnoughSegments
	"Find the power of two that represents a sufficient number of  
	segments for this cubic.  
	The measure is the sum of distances for the segments.  
	We want this to be close enough not affect the straightness of  
	the drawn lines. Which means within one pixel."
	"^ self 
	enough: 2 
	withMeasure: (self measureFor: 1) 
	withIn: self leeway 
	This ran into a problem when the curve was an s-curve with 
	inflections. Besides honeIn will check to see if 1 is better than 
	two so we lose nothing by starting a little higher."
	^ self
		enough: 4
		withMeasure: (self measureFor: 2)
		withIn: self leeway! !

!Cubic methodsFor: '*newCurves-cubic support' stamp: 'wiz 7/18/2004 22:50'!
enough: nTry withMeasure: lastMeasure withIn: closeEnough
"See comment in calcEnoughSegments for which I am a helper"
	| measure |
	measure := self measureFor: nTry.
	measure > (lastMeasure + closeEnough)
		ifFalse: [^ nTry // 2].
	^ self
		enough: 2 * nTry
		withMeasure: measure
		withIn: closeEnough! !

!Cubic methodsFor: '*newCurves-cubic support' stamp: 'wiz 6/17/2004 23:51'!
honeIn: enough 
	"Find if there is a smaller n than enough that give the same  
	measure for n."
	self
		assert: [enough isPowerOfTwo].
	enough < 2
		ifTrue: [^ enough].
	^ self
		honeIn: enough
		step: enough // 2
		measure: (self measureFor: enough)
		withIn: self leeway! !

!Cubic methodsFor: '*newCurves-cubic support' stamp: 'wiz 6/17/2004 23:45'!
honeIn: centerN step: step measure: measure withIn: closeEnough 
	"Pick the best n by binary search."
	| nTry |
	step < 1
		ifTrue: [^ centerN].
	nTry := centerN - step.
	^ measure > (closeEnough
				+ (self measureFor: nTry))
		ifTrue: [self
				honeIn: centerN
				step: step // 2
				measure: measure
				withIn: closeEnough]
		ifFalse: [self
				honeIn: nTry
				step: step // 2
				measure: measure
				withIn: closeEnough]! !

!Cubic methodsFor: '*newCurves-cubic support' stamp: 'wiz 6/19/2004 00:00'!
leeway
	"How close can measure be"
	^ 0.1! !

!Cubic methodsFor: '*newCurves-cubic support' stamp: 'wiz 7/7/2004 01:36'!
measureFor: n 
	"Return a distance measure for cubic curve with n segments.  
	For convienence and accuracy we use the sum or the distances."
	"first point is poly of 0."
	| p1 p2 measure |
	p1 := self first.
	measure := 0.
	(1 to: n)
		do: [:i | 
			p2 := self wizPolynomialEval: i / n asFloat.
			measure := measure
						+ (p2 dist: p1).
			p1 := p2].
	^ measure! !

NewCurveMorph initialize!

!Integer reorganize!
('testing' even isInteger isPowerOfTwo)
('arithmetic' * + - / // \\\ alignedTo: quo:)
('comparing' < = > hash)
('truncation and round off' asLargerPowerOfTwo asPowerOfTwo asSmallerPowerOfTwo atRandom atRandom: ceiling floor normalize rounded truncated)
('enumerating' timesRepeat:)
('mathematical functions' factorial gcd: lcm: raisedTo:modulo: take:)
('bit manipulation' << >> allMask: anyBitOfMagnitudeFrom:to: anyMask: bitAnd: bitClear: bitInvert bitInvert32 bitOr: bitShift: bitShiftMagnitude: bitXor: highBit highBitOfMagnitude lowBit noMask:)
('converting' adaptToFraction:andSend: adaptToScaledDecimal:andSend: asCharacter asColorOfDepth: asFloat asFraction asHexDigit asInteger asScaledDecimal: asYear)
('printing' asStringWithCommas asStringWithCommasSigned asTwoCharacterString asWords byteEncode:base: destinationBuffer: digitBuffer: hex hex8 isLiteral printOn:base: printOn:base:showRadix: printPaddedWith:to: printPaddedWith:to:base: printStringRadix: radix: romanString)
('system primitives' lastDigit replaceFrom:to:with:startingAt:)
('private' copyto: digitAdd: digitCompare: digitDiv:neg: digitLogic:op:length: digitLshift: digitMultiply:neg: digitRshift:bytes:lookfirst: digitSubtract: growby: growto: romanDigits:for:on:)
('benchmarks' benchFib benchmark tinyBenchmarks)
('tiles' asPrecedenceName)
('spline support')
('*newCurve-support' cacheForClosedCubicSolution)
!

"Postscript:
Do class initialzation to enable new curve behavior and update objects catalog."

NewCurveMorph initialize .


!


More information about the Squeak-dev mailing list