SceneKit-绘制3D抛物线 [英] SceneKit - Draw 3D Parabola

查看:130
本文介绍了SceneKit-绘制3D抛物线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我给了3分,需要画一个光滑的3D抛物线.麻烦的是,曲线断断续续,里面有些奇怪的东西

I'm given three points and need to draw a smooth 3D parabola. The trouble is that curved line is choppy and has some weird divots in it

这是我的代码...

func drawJump(jump: Jump){
    let halfDistance = jump.distance.floatValue/2 as Float
    let tup = CalcParabolaValues(0.0, y1: 0.0, x2: halfDistance, y2: jump.height.floatValue, x3: jump.distance.floatValue, y3: 0)
    println("tuple \tup")

    var currentX = 0 as Float
    var increment = jump.distance.floatValue / Float(50)
    while currentX < jump.distance.floatValue - increment {

        let x1 = Float(currentX)
        let x2 = Float((currentX+increment))
        let y1 = calcParabolaYVal(tup.a, b: tup.b, c: tup.c, x: x1)
        let y2 = calcParabolaYVal(tup.a, b: tup.b, c: tup.c, x: x2)

        drawLine(x1, y1: y1, x2: x2, y2: y2)

        currentX += increment
    }
}

func CalcParabolaValues(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) -> (a: Float, b: Float, c: Float) {
    println(x1, y1, x2, y2, x3, y3)
    let a = y1/((x1-x2)*(x1-x3)) + y2/((x2-x1)*(x2-x3)) + y3/((x3-x1)*(x3-x2))
    let b = (-y1*(x2+x3)/((x1-x2)*(x1-x3))-y2*(x1+x3)/((x2-x1)*(x2-x3))-y3*(x1+x2)/((x3-x1)*(x3-x2)))
    let c = (y1*x2*x3/((x1-x2)*(x1-x3))+y2*x1*x3/((x2-x1)*(x2-x3))+y3*x1*x2/((x3-x1)*(x3-x2)))

    return (a, b, c)
}

func calcParabolaYVal(a:Float, b:Float, c:Float, x:Float)->Float{
    return a * x * x + b * x + c
}

func drawLine(x1: Float, y1: Float,x2: Float, y2: Float) {
    println("drawLine \(x1) \(y1) \(x2) \(y2)")
    let positions: [Float32] = [
        x1, y1, 0,
        x2, y2, 0
    ]
    let positionData = NSData(bytes: positions, length: sizeof(Float32)*positions.count)
    let indices: [Int32] = [0, 1]
    let indexData = NSData(bytes: indices, length: sizeof(Int32) * indices.count)

    let source = SCNGeometrySource(data: positionData, semantic: SCNGeometrySourceSemanticVertex, vectorCount: indices.count, floatComponents: true, componentsPerVector: 3, bytesPerComponent: sizeof(Float32), dataOffset: 0, dataStride: sizeof(Float32) * 3)
    let element = SCNGeometryElement(data: indexData, primitiveType: SCNGeometryPrimitiveType.Line, primitiveCount: indices.count, bytesPerIndex: sizeof(Int32))

    let line = SCNGeometry(sources: [source], elements: [element])
    self.rootNode.addChildNode( SCNNode(geometry: line))
}

func renderer(aRenderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
    glLineWidth(20)
}

我还必须弄清楚如何从左到右为圆弧设置动画.有人可以帮我吗? Swift或Objective C都很好.任何帮助表示赞赏.谢谢!

I also have to figure out how to animate the arc from left to right. Can someone help me out? Swift or Objective C is fine. Any help is appreciated. Thanks!

推荐答案

我建议使用 UIBezierPath .对于动画,我个人认为着色器修饰符非常合适对于这种情况.

I'd recommend using SCNShape to create your parabola. To start, you'll need to represent your parabola as a Bézier curve. You can use UIBezierPath for that. For animation, I personally find shader modifiers are a nice fit for cases like this.

但是请注意-您可能想要一条仅代表弧的开放笔触的路径.如果您执行以下操作:

Watch out, though — you probably want a path that represents just the open stroke of the arc. If you do something like this:

let path = UIBezierPath()
path.moveToPoint(CGPointZero)
path.addQuadCurveToPoint(CGPoint(x: 100, y: 0), controlPoint: CGPoint(x: 50, y: 200))

您将获得一个这样的填充的抛物线(在调试器中以2D形式快速查看,然后以SCNShape的形式在3D中拉伸):

You'll get a filled-in parabola, like this (seen in 2D in the debugger quick look, then extruded in 3D with SCNShape):

要创建仅是弧形的闭合形状,您需要在曲线上追溯,与原始曲线略有不同:

To create a closed shape that's just the arc, you'll need to trace back over the curve, a little bit away from the original:

let path = UIBezierPath()
path.moveToPoint(CGPointZero)
path.addQuadCurveToPoint(CGPoint(x: 100, y: 0), controlPoint: CGPoint(x: 50, y: 200))
path.addLineToPoint(CGPoint(x: 99, y: 0))
path.addQuadCurveToPoint(CGPoint(x: 1, y: 0), controlPoint: CGPoint(x: 50, y: 198))

>

那更好.

如何使其真正成为3D?只需用您喜欢的拉伸深度制作一个SCNShape:

How to actually make it 3D? Just make an SCNShape with the extrusion depth you like:

let shape = SCNShape(path: path, extrusionDepth: 10)

并将其设置在您的场景中:

And set it in your scene:

shape.firstMaterial?.diffuse.contents = SKColor.blueColor()
let shapeNode = SCNNode(geometry: shape)
shapeNode.pivot = SCNMatrix4MakeTranslation(50, 0, 0)
shapeNode.eulerAngles.y = Float(-M_PI_4)
root.addChildNode(shapeNode)

在这里,我使用pivot使形状围绕抛物线的主轴旋转,而不是平面贝塞尔曲线的y = 0轴旋转.并使其变蓝.另外,root只是我为视图场景的根节点创建的快捷方式.

Here I'm using a pivot to make the shape rotate around the major axis of the parabola, instead of the y = 0 axis of the planar Bézier curve. And making it blue. Also, root is just a shortcut I made for the view's scene's root node.

抛物线的形状实际上并不需要通过动画进行更改-您只需要一个视觉效果即可沿其x轴逐渐显示它. 着色器修改器非常适合这样做,因为您可以使动画效果按像素而不是按顶点,并在GPU上完成所有昂贵的工作.

The shape of the parabola doesn't really need to change through your animation — you just need a visual effect that progressively reveals it along its x-axis. Shader modifiers are a great fit for that, because you can make the animated effect per-pixel instead of per-vertex and do all the expensive work on the GPU.

以下是使用progress参数(从0到1)来设置基于x位置的不透明度的着色器代码段:

Here's a shader snippet that uses a progress parameter, varying from 0 to 1, to set opacity based on x-position:

// declare a variable we can set from SceneKit code
uniform float progress;
// tell SceneKit this shader uses transparency so we get correct draw order
#pragma transparent
// get the position in model space
vec4 mPos = u_inverseModelViewTransform * vec4(_surface.position, 1.0);
// a bit of math to ramp the alpha based on a progress-adjusted position
_surface.transparent.a = clamp(1.0 - ((mPos.x + 50.0) - progress * 200.0) / 50.0, 0.0, 1.0);

将其设置为Surface入口点的着色器修改器,然后可以为progress变量设置动画:

Set that as a shader modifier for the Surface entry point, and then you can animate the progress variable:

let modifier = "uniform float progress;\n #pragma transparent\n vec4 mPos = u_inverseModelViewTransform * vec4(_surface.position, 1.0);\n _surface.transparent.a = clamp(1.0 - ((mPos.x + 50.0) - progress * 200.0) / 50.0, 0.0, 1.0);"
shape.shaderModifiers = [ SCNShaderModifierEntryPointSurface: modifier ]
shape.setValue(0.0, forKey: "progress")

SCNTransaction.begin()
SCNTransaction.setAnimationDuration(10)
shape.setValue(1.0, forKey: "progress")
SCNTransaction.commit()

这就是全部内容,您可以将其粘贴到(iOS)游乐场中.留给读者练习的一些内容,以及其他说明:

Here's the whole thing in a form you can paste into a (iOS) playground. A few things left as exercises to the reader, plus other notes:

  • 找出幻数并创建函数或类,以便您可以更改抛物线的大小/形状. (请记住,您可以相对于其他场景元素缩放SceneKit节点,因此它们不必使用相同的单位.)

  • Factor out the magic numbers and make a function or class so you can alter the size/shape of your parabola. (Remember that you can scale SceneKit nodes relative to other scene elements, so they don't have to use the same units.)

将抛物线相对于其他场景元素定位.如果删除我设置pivot的行,则shapeNode.position是抛物线的左端.更改抛物线的长度(或缩放),然后绕其y轴旋转,即可使另一端与其他节点对齐. (让您向…发射ze-ze导弹吗?)

Position the parabola relative to other scene elements. If you take away my line that sets the pivot, the shapeNode.position is the left end of the parabola. Change the parabola's length (or scale it), then rotate it around its y-axis, and you can make the other end line up with some other node. (For you to fire ze missiles at?)

我将其与Swift 2 beta一起使用,但我认为其中没有任何Swift-2特定的语法-如果您需要尽快部署,则移植回1.2应该很简单.

I threw this together with Swift 2 beta, but I don't think there's any Swift-2-specific syntax in there — porting back to 1.2 if you need to deploy soon should be straightforward.

如果您还想在OS X上执行此操作,则有点棘手-在那里,SCNShape使用NSBezierPath,与UIBezierPath不同,它不支持二次曲线.也许一个简单的方法是使用椭圆弧来伪造它.

If you also want to do this on OS X, it's a bit trickier — there, SCNShape uses NSBezierPath, which unlike UIBezierPath doesn't support quadratic curves. Probably an easy way out would be to fake it with an elliptical arc.

这篇关于SceneKit-绘制3D抛物线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆