问题描述
我给了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抛物线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!