deadsy / sdfx

A simple CAD package using signed distance functions
MIT License
535 stars 52 forks source link

extrude along curve #17

Open summer-tree opened 4 years ago

summer-tree commented 4 years ago

Hello. Is there any way to extrude shape along curve?

deadsy commented 4 years ago

It's not currently supported. In general you'd need to define the curve, a normal to the curve, and the 2d shape you want to extrude. Then the tricky part is working out the SDF for such a thing- suffice to say that you can't just map the 3d coordinate back to the plane of the 2d shape- it's tricky, you'd probably need some sort of iterative method.

soypat commented 2 years ago

Well I gave it a shot and fell flat on my face when I had to take the orthogonal projection of a vector over a plane. I have it more or less thought out but I can't find an algorithm for doing said operation so I'll leave my code here if someone wants to pick it up immediately. I think I might give it a shot again sometime in the future

// Patricio Whittingslow Copyleft 2022
// LICENSE: WTFPL

package sdf_test

import (
    "testing"

    "github.com/deadsy/sdfx/render"
    "github.com/deadsy/sdfx/sdf"
)

type CurveExtrude struct {
    // Parametrized curve
    f  func(t float64) sdf.V3
    g  func(t float64) sdf.SDF2
    dt float64
}

const (
    L = 10
    H = 3
    W = 4
)

func TestArb(t *testing.T) {
    shape := sdf.Box2D(sdf.V2{H, W}, H/10)
    a := CurveExtrude{
        f: func(t float64) sdf.V3 {
            return sdf.V3{t * L, 0, 0}
        },
        g: func(t float64) sdf.SDF2 {
            return shape
        },
        dt: L / 20,
    }
    render.RenderSTLSlow(a, 100, "extrude")
}

func (a CurveExtrude) BoundingBox() sdf.Box3 {
    return sdf.NewBox3(sdf.V3{L / 2, 0, 0}, sdf.V3{L, 0, 0})
}

func (a CurveExtrude) Evaluate(p sdf.V3) float64 {
    dt2 := a.dt / 2
    for t := a.dt; t <= 1; t += a.dt {
        // We define the plane where the SDF2 lives with center (point) and n (vector).
        center := a.f(t) // Center point of evaluation.
        // Acquire the 3D direction of the curve. The normal vector.
        n := a.f(t + dt2)
        n = n.Sub(a.f(t - dt2))
        // We obtain the projection of p onto the plane. this will let us know how to transform p.
        pv := p.Sub(center) // vector to point.

    }
    return 1 //TODO
}

func v3cos(a, b sdf.V3) float64 {
    return a.Dot(b) / (a.Length() * b.Length())
}
deadsy commented 2 years ago

The loop in the evaluate function is going to kill performance - that's why I suggested an iterative solution. In many cases the solution for a new point is likely close to the solution for neighboring points. If you already have an ok solution - an iteration or two may make it a very good solution.

soypat commented 2 years ago

So there's a pattern to the points received by Evaluate method? Sequential calls of Evaluate will have points that tend to be near to eachother?

deadsy commented 2 years ago

Sequential calls of Evaluate will have points that tend to be near to each other

Well - yes (as a practical matter) but in general no. ie - the calls to evaluate are independent and some renderers will have calls all over the place. Still - if you can do some form of solution caching it may pay-off. Finding a cache entry that is "close" to an existing entry is an interesting problem.

deadsy commented 2 years ago

btw - this method is not unlike that for working out screw threads where the evaluation point is projected onto the 2d thread form. The screw thread evaluation is slightly incorrect (it doesn't work properly with the octree based renderer) and I think this more general method is also going to be wrong- although probably still useful with the uniform cube renderer.