SceneKit: dibuja la parábola en 3D

Me han dado tres puntos y necesito dibujar una parábola 3D suave. El problema es que la línea curva está entrecortada y tiene divots extraños en ella

parábola

Aquí está mi código …

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

También tengo que averiguar cómo animar el arco de izquierda a derecha. ¿Alguien me puede ayudar? Swift o Objective C está bien. Cualquier ayuda es apreciada. ¡Gracias!

Recomiendo el uso de SCNShape para crear su parábola. Para comenzar, necesitarás representar tu parábola como una curva de Bézier. Puedes usar UIBezierPath para eso. Para la animation, personalmente encuentro que los modificadores de sombreador son un buen ajuste para casos como este.

La parábola

Tenga cuidado, sin embargo, es probable que desee una ruta que represente solo el recorrido abierto del arco. Si haces algo como esto:

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

Obtendrás una parábola rellenada, como esta (vista en 2D en el depurador, mira rápidamente y luego se extruye en 3D con SCNShape ):

parábola llenaparábola llena en 3D

Para crear una forma cerrada que sea solo el arco, necesitará rastrear la curva, un poco lejos del 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)) 

parábola abiertaabrir parábola en 3D

Eso es mejor.

… en Three-Dee!

¿Cómo hacerlo realmente 3D? Simplemente haga un SCNShape con la profundidad de extrusión que desee:

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

Y colócalo en tu escena:

 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) 

Aquí estoy usando un pivot para hacer que la forma gire alnetworkingedor del eje principal de la parábola, en lugar del eje y = 0 de la curva plana Bézier. Y haciendo que sea azul. Además, root es solo un atajo que hice para el nodo raíz de la escena de la vista.

Animando

La forma de la parábola no necesita cambiar a través de su animation; solo necesita un efecto visual que lo revele progresivamente a lo largo de su eje x. Los modificadores de sombreador son ideales para eso, ya que puedes hacer el efecto animado por píxel en lugar de por vértice y hacer todo el trabajo costoso en la GPU.

Aquí hay un fragment de sombreado que utiliza un parámetro de progress , que varía de 0 a 1, para establecer la opacidad según la position x:

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

Configúrelo como un modificador de sombreado para el punto de input de Superficie y, a continuación, puede animar la variable de progress :

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

animando parábola

Consideraciones adicionales

Aquí está todo en un formulario que puede pegar en un patio de juego (iOS). Algunas cosas quedan como ejercicios para el lector, más otras notas:

  • Factoriza los numbers mágicos y crea una function o class para que puedas modificar el tamaño o la forma de tu parábola. (Recuerde que puede escalar los nodos de SceneKit en relación con otros elementos de la escena, para que no tengan que usar las mismas unidades).

  • Coloque la parábola en relación con otros elementos de la escena. Si quitas mi línea que establece el pivot , shapeNode.position es el extremo izquierdo de la parábola. Cambie la longitud de la parábola (o escala), luego gírela alnetworkingedor de su eje y, y puede alinear la otra punta con algún otro nodo. (¿Para que dispares misiles ze? )

  • Junté esto con Swift 2 beta, pero no creo que haya ninguna syntax Swift-2 específica allí; volver a 1.2 si necesita desplegar pronto debería ser sencillo.

  • Si también quieres hacer esto en OS X, es un poco más complicado; allí, SCNShape usa NSBezierPath , que a diferencia de UIBezierPath no admite curvas cuadráticas. Probablemente, una salida fácil sería simularlo con un arco elíptico.

No creo que tu table tenga suficientes puntos, suponiendo que el procesador los conecte con segmentos de línea recta. Además de esto, el grosor y el trazo de la línea hacen que sea difícil ver eso. Intenta get una curva suave con una delgada línea sólida primero.

Si desea animar la progresión de la curva, como si estuviera mostrando el vuelo de un proyectil, probablemente sea más fácil simplemente escribir su function para el movimiento: y = k * x ^ 2, y simplemente renderizar desde x = 0 a x = T para boost los valores de T.