Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to draw filled closed Catmull-Rom curves using paths? #65

Open
thomastthai opened this issue Mar 13, 2025 · 0 comments
Open

How to draw filled closed Catmull-Rom curves using paths? #65

thomastthai opened this issue Mar 13, 2025 · 0 comments

Comments

@thomastthai
Copy link

thomastthai commented Mar 13, 2025

I may have missed something in the API or docs. I couldn't find a way to draw paths using Catmull-Rom curves that form an enclosed loop and can be filled with a color.

The head in the image was created using:

		p5.Stroke(color.Black)
		p5.Fill(color.White)

		p5.Curve(
			p1.x, p1.y,
			p2.x, p2.y,
			p3.x, p3.y,
			p4.x, p4.y,
		)

The p5.Fill(color.White) didn't color the head white. I think it could be done if I used paths instead.

Image

path.go showed I may need to get an instance of proc and use proc.BeginPath(). Since newProc() and gproc are not exported, how do you get a proc instance?

p5.go:

var (
	// gproc is the global Proc instance used by the p5js-like API.
	gproc = newProc(defaultWidth, defaultHeight)
)

Additionally, the code for Catmull-Rom curve is not in path.go but is in shape.go. Shouldn't that code be refactored and placed in path.go like the Bezier() and func (p *Proc) Curve(x1, y1, x2, y2, x3, y3, x4, y4 float64) use that code from path.go?

/ Curve draws a curved line starting at (x2,y2) and ending at (x3,y3).
// (x1,y1) and (x4,y4) are the control points.
//
// Curve is an implementation of Catmull-Rom splines.
func (p *Proc) Curve(x1, y1, x2, y2, x3, y3, x4, y4 float64) {
	// Convert the Catmull-Rom curve into a cubic Bézier curve according to
	//
	//  "Conversion Between Bézier and Catmull-Rom Splines"
	//  S. Arasteh and A. Kalisz
	//
	// An electronic version is available at:
	//  https://arxiv.org/pdf/2011.08232.pdf

	tau := p.stk.cur().tau
	if tau == 1 {
		p.Line(x2, y2, x3, y3)
		return
	}

	var (
		cr1 = p.pt(x1, y1)
		cr2 = p.pt(x2, y2)
		cr3 = p.pt(x3, y3)
		cr4 = p.pt(x4, y4)

		itau = 1 / (6 * (1 - tau))

		p1 = cr2
		p2 = cr2.Add(cr3.Sub(cr1).Mul(itau))
		p3 = cr3.Sub(cr4.Sub(cr2).Mul(itau))
		p4 = cr3

		path clip.Path

		sp  = p1
		cp0 = p2.Sub(sp)
		cp1 = p3.Sub(sp)
		ep  = p4.Sub(sp)
	)

	defer op.Save(p.ctx.Ops).Load()

	path.Begin(p.ctx.Ops)
	path.Move(sp)
	path.Cube(cp0, cp1, ep)

	paint.FillShape(
		p.ctx.Ops,
		rgba(p.stk.cur().stroke.color),
		clip.Stroke{
			Path:  path.End(),
			Style: p.stk.cur().stroke.style,
		}.Op(),
	)
}

// CurveTightness determines how the curve fits to the Curve vertex points.
// CurveTightness controls the Catmull-Rom tau tension.
//
// The default value is 0.
func (p *Proc) CurveTightness(v float64) {
	p.stk.cur().tau = float32(v)
}

On a related note, the documentation stated:

p5 actually provides two set of APIs:

  • one closely following the p5js API, with global functions and hidden state,
  • another one based on the p5.Proc type that encapsulates state.

How is the p5.Proc API used if newProc() is not exported?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant