Рисование линии между двумя точками с помощью SceneKit
У меня есть две точки (назовем их pointA и pointB) типа SCNVector3. Я хочу провести между ними линию. Похоже, это должно быть легко, но не может найти способ сделать это.
Я вижу два варианта, у обоих есть проблемы:
Используйте SCNCylinder с очень маленьким радиусом, с длиной | pointA-pointB | а затем поместите его/поверните.
Используйте пользовательскую SCNGeometry, но не знаете, как; нужно было бы определить два треугольника, чтобы сформировать очень тонкий прямоугольник, возможно?
Кажется, должен быть более простой способ сделать это, но я не могу найти его.
Изменить: использование метода треугольника дает мне это для рисования линии между (0,0,0) и (10,10,10):
CGFloat delta = 0.1;
SCNVector3 positions[] = { SCNVector3Make(0,0,0),
SCNVector3Make(10, 10, 10),
SCNVector3Make(0+delta, 0+delta, 0+delta),
SCNVector3Make(10+delta, 10+delta, 10+delta)};
int indicies[] = {
0,2,1,
1,2,3
};
SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions count:4];
NSData *indexData = [NSData dataWithBytes:indicies length:sizeof(indicies)];
SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:2 bytesPerIndex:sizeof(int)];
SCNGeometry *line = [SCNGeometry geometryWithSources:@[vertexSource] elements:@[element]];
SCNNode *lineNode = [SCNNode nodeWithGeometry:line];
[root addChildNode:lineNode];
Но есть проблемы: из-за нормалей вы можете видеть эту линию только с одной стороны! Он невидим с другой стороны. Кроме того, если "delta" слишком мала, вы не видите линию вообще. Как бы то ни было, это технически прямоугольник, а не линия, для которой я собирался, что может привести к небольшим графическим сбоям, если я хочу нарисовать несколько связанных строк.
Ответы
Ответ 1
Есть много способов сделать это.
Как уже отмечалось, ваш пользовательский геометрический подход имеет некоторые недостатки. Вы должны уметь исправить проблему его невидимости с одной стороны, предоставив свой материал doubleSided
. Тем не менее, у вас могут быть проблемы с тем, что они двумерные.
Вы также можете изменить свою настраиваемую геометрию, чтобы включить больше треугольников, поэтому вы получите форму трубки с тремя или более сторонами вместо плоского прямоугольника. Или просто укажите две точки в источнике геометрии и используйте тип элемента геометрии SCNGeometryPrimitiveTypeLine
, чтобы набор сцен выделил между ними сегмент линии. (Хотя вы не получите столько гибкости в стилях рендеринга с рисованием линии, как с затененными полигонами.)
Вы также можете использовать описанный вами подход SCNCylinder
(или любую другую встроенную примитивную форму). Помните, что геометрия определяется в их собственном локальном (aka Model) координатном пространстве, которое Scene Kit интерпретирует относительно координатного пространства, определенного node. Другими словами, вы можете определить цилиндр (или коробку, капсулу или плоскость или что-то еще), равное 1,0 единицам во всех измерениях, а затем использовать поворот/масштаб/положение или преобразование SCNNode
, содержащий эту геометрию, чтобы сделать ее длинной, тонкой и растягивающейся между двумя точками, которые вы хотите. (Также обратите внимание: поскольку ваша линия будет довольно тонкой, вы можете уменьшить segmentCount
любой встроенной геометрии, которую вы используете, потому что эта деталь не будет видна.)
Еще один вариант - это класс SCNShape
, который позволяет создавать экструдированный 3D-объект из пути 2D Bézier. Разработка правильного преобразования для получения плоскости, соединяющей две произвольные точки, звучит как забавная математика, но как только вы это сделаете, вы можете легко соединить свои точки с любой формой линии, которую вы выберете.
Ответ 2
Вот простое расширение в Swift:
extension SCNGeometry {
class func lineFrom(vector vector1: SCNVector3, toVector vector2: SCNVector3) -> SCNGeometry {
let indices: [Int32] = [0, 1]
let source = SCNGeometrySource(vertices: [vector1, vector2])
let element = SCNGeometryElement(indices: indices, primitiveType: .Line)
return SCNGeometry(sources: [source], elements: [element])
}
}
Ответ 3
Новый код для строки от (0, 0, 0) до (10, 10, 10) ниже.
Я не уверен, что его можно было бы улучшить.
SCNVector3 positions[] = {
SCNVector3Make(0.0, 0.0, 0.0),
SCNVector3Make(10.0, 10.0, 10.0)
};
int indices[] = {0, 1};
SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions
count:2];
NSData *indexData = [NSData dataWithBytes:indices
length:sizeof(indices)];
SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData
primitiveType:SCNGeometryPrimitiveTypeLine
primitiveCount:1
bytesPerIndex:sizeof(int)];
SCNGeometry *line = [SCNGeometry geometryWithSources:@[vertexSource]
elements:@[element]];
SCNNode *lineNode = [SCNNode nodeWithGeometry:line];
[root addChildNode:lineNode];
Ответ 4
Здесь одно решение
class func lineBetweenNodeA(nodeA: SCNNode, nodeB: SCNNode) -> SCNNode {
let positions: [Float32] = [nodeA.position.x, nodeA.position.y, nodeA.position.z, nodeB.position.x, nodeB.position.y, nodeB.position.z]
let positionData = NSData(bytes: positions, length: MemoryLayout<Float32>.size*positions.count)
let indices: [Int32] = [0, 1]
let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count)
let source = SCNGeometrySource(data: positionData as Data, semantic: SCNGeometrySource.Semantic.vertex, vectorCount: indices.count, usesFloatComponents: true, componentsPerVector: 3, bytesPerComponent: MemoryLayout<Float32>.size, dataOffset: 0, dataStride: MemoryLayout<Float32>.size * 3)
let element = SCNGeometryElement(data: indexData as Data, primitiveType: SCNGeometryPrimitiveType.line, primitiveCount: indices.count, bytesPerIndex: MemoryLayout<Int32>.size)
let line = SCNGeometry(sources: [source], elements: [element])
return SCNNode(geometry: line)
}
если вы хотите обновить ширину строки или что-либо, связанное с изменением свойств рисованной линии, вы захотите использовать один из вызовов openGL в обратном вызове рендеринга SceneKit:
func renderer(aRenderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
//Makes the lines thicker
glLineWidth(20)
}
Ответ 5
Таким образом, внутри вашего ViewController.cs определите ваши векторные точки и вызовите функцию Draw, а затем на последней строке - просто поверните ее, чтобы посмотреть на точку b.
var a = someVector3;
var b = someOtherVector3;
nfloat cLength = (nfloat)Vector3Helper.DistanceBetweenPoints(a, b);
var cyclinderLine = CreateGeometry.DrawCylinderBetweenPoints(a, b, cLength, 0.05f, 10);
ARView.Scene.RootNode.Add(cyclinderLine);
cyclinderLine.Look(b, ARView.Scene.RootNode.WorldUp, cyclinderLine.WorldUp);
Создайте статический класс CreateGeomery и поместите в него этот статический метод.
public static SCNNode DrawCylinderBetweenPoints(SCNVector3 a,SCNVector3 b, nfloat length, nfloat radius, int radialSegments){
SCNNode cylinderNode;
SCNCylinder cylinder = new SCNCylinder();
cylinder.Radius = radius;
cylinder.Height = length;
cylinder.RadialSegmentCount = radialSegments;
cylinderNode = SCNNode.FromGeometry(cylinder);
cylinderNode.Position = Vector3Helper.GetMidpoint(a,b);
return cylinderNode;
}
вы также можете захотеть эти служебные методы в статическом вспомогательном классе
public static double DistanceBetweenPoints(SCNVector3 a, SCNVector3 b)
{
SCNVector3 vector = new SCNVector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
return Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z);
}
public static SCNVector3 GetMidpoint(SCNVector3 a, SCNVector3 b){
float x = (a.X + b.X) / 2;
float y = (a.Y + b.Y) / 2;
float z = (a.Z + b.Z) / 2;
return new SCNVector3(x, y, z);
}
Для всех моих друзей Xamarin С# там.
Ответ 6
Здесь решение с использованием треугольников, которое работает независимо от направления линии. Он построен с использованием перекрестного произведения, чтобы получить точки, перпендикулярные линии. Поэтому вам понадобится небольшое расширение SCNVector3, но оно, вероятно, пригодится и в других случаях.
private func makeRect(startPoint: SCNVector3, endPoint: SCNVector3, width: Float ) -> SCNGeometry {
let dir = (endPoint - startPoint).normalized()
let perp = dir.cross(SCNNode.localUp) * width / 2
let firstPoint = startPoint + perp
let secondPoint = startPoint - perp
let thirdPoint = endPoint + perp
let fourthPoint = endPoint - perp
let points = [firstPoint, secondPoint, thirdPoint, fourthPoint]
let indices: [UInt16] = [
1,0,2,
1,2,3
]
let geoSource = SCNGeometrySource(vertices: points)
let geoElement = SCNGeometryElement(indices: indices, primitiveType: .triangles)
let geo = SCNGeometry(sources: [geoSource], elements: [geoElement])
geo.firstMaterial?.diffuse.contents = UIColor.blue.cgColor
return geo
}
Расширение SCNVector3:
import Foundation
import SceneKit
extension SCNVector3
{
/**
* Returns the length (magnitude) of the vector described by the SCNVector3
*/
func length() -> Float {
return sqrtf(x*x + y*y + z*z)
}
/**
* Normalizes the vector described by the SCNVector3 to length 1.0 and returns
* the result as a new SCNVector3.
*/
func normalized() -> SCNVector3 {
return self / length()
}
/**
* Calculates the cross product between two SCNVector3.
*/
func cross(_ vector: SCNVector3) -> SCNVector3 {
return SCNVector3(y * vector.z - z * vector.y, z * vector.x - x * vector.z, x * vector.y - y * vector.x)
}
}
Ответ 7
Вот версия swift5:
func lineBetweenNodes(positionA: SCNVector3, positionB: SCNVector3, inScene: SCNScene) -> SCNNode {
let vector = SCNVector3(positionA.x - positionB.x, positionA.y - positionB.y, positionA.z - positionB.z)
let distance = sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
let midPosition = SCNVector3 (x:(positionA.x + positionB.x) / 2, y:(positionA.y + positionB.y) / 2, z:(positionA.z + positionB.z) / 2)
let lineGeometry = SCNCylinder()
lineGeometry.radius = 0.05
lineGeometry.height = distance
lineGeometry.radialSegmentCount = 5
lineGeometry.firstMaterial!.diffuse.contents = GREEN
let lineNode = SCNNode(geometry: lineGeometry)
lineNode.position = midPosition
lineNode.look (at: positionB, up: inScene.rootNode.worldUp, localFront: lineNode.worldUp)
return lineNode
}