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

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

问题描述

我得到了三个点,需要绘制一条平滑的 3D 抛物线.问题是曲线断断续续,里面有一些奇怪的草皮

这是我的代码...

func drawJump(jump: Jump){让 halfDistance = jump.distance.floatValue/2 as Float让 tup = CalcParabolaValues(0.0, y1: 0.0, x2: halfDistance, y2: jump.height.floatValue, x3: jump.distance.floatValue, y3: 0)println("元组	up")var currentX = 0 作为浮点数var increment = jump.distance.floatValue/Float(50)而 currentX (a: 浮动, b: 浮动, c: 浮动) {打印(x1,y1,x2,y2,x3,y3)让 a = y1/((x1-x2)*(x1-x3)) + y2/((x2-x1)*(x2-x3)) + y3/((x3-x1)*(x3-x2))让 b = (-y1*(x2+x3)/((x1-x2)*(x1-x3))-y2*(x1+x3)/((x2-x1)*(x2-x3))-y3*(x1+x2)/((x3-x1)*(x3-x2)))让 c = (y1*x2*x3/((x1-x2)*(x1-x3))+y2*x1*x3/((x2-x1)*(x2-x3))+y3*x1*x2/((x3-x1)*(x3-x2)))返回 (a, b, c)}func calcParabolaYVal(a:Float, b:Float, c:Float, x:Float)->Float{返回 a * x * x + b * x + c}func drawLine(x1: Float, y1: Float,x2: Float, y2: Float) {println("画线 (x1) (y1) (x2) (y2)")让位置:[Float32] = [x1, y1, 0,x2, y2, 0]让 positionData = NSData(字节:位置,长度:sizeof(Float32)*positions.count)让索引:[Int32] = [0, 1]让 indexData = NSData(字节:索引,长度:sizeof(Int32)* index.count)让源 = SCNGeometrySource(数据:位置数据,语义:SCNGeometrySourceSemanticVertex,vectorCount:indices.count,floatComponents:true,componentsPerVector:3,bytesPerComponent:sizeof(Float32),dataOffset:0,d​​ataStride:sizeof(Float32)* 3)让元素 = SCNGeometryElement(数据:indexData,primitiveType:SCNGeometryPrimitiveType.Line,primitiveCount:indices.count,bytesPerIndex:sizeof(Int32))让 line = SCNGeometry(sources: [source], elements: [element])self.rootNode.addChildNode(SCNNode(geometry: line))}func renderer(aRenderer: SCNSceneRenderer, willRenderScene 场景: SCNScene, atTime 时间: NSTimeInterval) {格线宽度(20)}

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

解决方案

我建议使用 非常适合这种情况,因为您可以为每个像素而不是每个顶点制作动画效果,并在 GPU 上完成所有昂贵的工作.

这是一个着色器片段,它使用 progress 参数(从 0 到 1)根据 x 位置设置不透明度:

//声明一个我们可以从 SceneKit 代码中设置的变量统一浮动进度;//告诉 SceneKit 这个着色器使用透明度所以我们得到正确的绘制顺序#pragma 透明//获取模型空间中的位置vec4 mPos = u_inverseModelViewTransform * vec4(_surface.position, 1.0);//根据进度调整位置增加 alpha 的一些数学运算_surface.transparent.a =clamp(1.0 - ((mPos.x + 50.0) - progress * 200.0)/50.0, 0.0, 1.0);

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

let modifier = "uniform float progress;
 #pragma transparent
 vec4 mPos = u_inverseModelViewTransform * vec4(_surface.position, 1.0);
 _surface.transparent.a = clip(1.0 - ((mPos.x + 50.0) - 进度 * 200.0)/50.0, 0.0, 1.0);"shape.shaderModifiers = [ SCNShaderModifierEntryPointSurface: 修饰符 ]shape.setValue(0.0, forKey: "progress")SCNTransaction.begin()SCNTransaction.setAnimationDuration(10)shape.setValue(1.0, forKey: "progress")SCNTransaction.commit()

进一步的考虑

全部内容,您可以将其粘贴到 (iOS) 游乐场中.一些事情留给读者作为练习,以及其他注意事项:

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

  • 相对于其他场景元素定位抛物线.如果你去掉我设置 pivot 的线,shapeNode.position 就是抛物线的左端.更改抛物线的长度(或缩放它),然后围绕其 y 轴旋转它,您可以使另一端与其他节点对齐.(让你发射导弹?)

  • 我将它与 Swift 2 测试版放在一起,但我认为其中没有任何特定于 Swift-2 的语法——如果您需要尽快部署,移植回 1.2 应该很简单.

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

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

Here is my code...

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 	up")

    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)
}

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!

解决方案

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.

The Parabola

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))

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))

That's better.

... in Three-Dee!

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)

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.

Animating

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.

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);

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

let modifier = "uniform float progress;
 #pragma transparent
 vec4 mPos = u_inverseModelViewTransform * vec4(_surface.position, 1.0);
 _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()

Further Considerations

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:

  • 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.)

  • 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?)

  • 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.

  • 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天全站免登陆