web-dev-qa-db-ja.com

SceneKitを使用して2点間に線を引く

タイプSCNVector3の2つのポイント(pointAとpointBと呼びます)があります。それらの間に線を引きたい。簡単なようですが、それを行う方法が見つかりません。

2つのオプションがあり、どちらにも問題があります。

半径が非常に小さく、長さが| pointA-pointB |のSCNCylinderを使用しますそして、それを配置/回転させます。

カスタムSCNGeometryを使用しますが、方法がわかりません。非常に薄い長方形を形成するために2つの三角形を定義する必要があるでしょうか?

これを行う簡単な方法があるはずですが、見つけられないようです。

編集:三角形の方法を使用すると、(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];

しかし、問題があります。法線により、この線は片側からしか見えません!向こうからは見えない。また、「デルタ」が小さすぎると、線がまったく見えなくなります。実際のところ、それは技術的には目的の線ではなく長方形であり、複数の結合された線を描画したい場合に小さなグラフィックの不具合が発生する可能性があります。

21
Matthew

これを行う方法はたくさんあります。

前述のように、カスタムジオメトリアプローチにはいくつかの欠点があります。マテリアルに doubleSided プロパティを指定することで、片側から見えなくなるという問題を修正できるはずです。ただし、2次元であることにはまだ問題があるかもしれません。

また、カスタムジオメトリを変更してより多くの三角形を含めることにより、平らな長方形の代わりに3つ以上の側面を持つチューブシェイプを作成することもできます。または、ジオメトリソースに2つのポイントがあり、SCNGeometryPrimitiveTypeLineジオメトリ要素タイプを使用して、シーンキットにそれらの間に線分を描画させます。 (ただし、線描画を使用したスタイルのレンダリングでは、シェーディングされたポリゴンの場合ほど柔軟性はありません。)

先ほど触れたSCNCylinderアプローチ(または他の組み込みプリミティブシェイプ)を使用することもできます。ジオメトリは独自のローカル(別名モデル)座標空間で定義されていることを思い出してください。これは、シーンキットがノードによって定義された座標空間を基準に解釈します。つまり、すべての次元で幅が1.0単位の円柱(またはボックス、カプセル、平面など)を定義し、そのジオメトリを含むSCNNodeの回転/スケール/位置または変換を使用して、それは長くて薄く、あなたが望む2つのポイントの間で伸びます。 (また、ラインがかなり細くなるので、使用している組み込みジオメトリのsegmentCountsを減らすことができることに注意してください。その詳細は表示されません。)

さらにもう1つのオプションは、2Dベジエ曲線から押し出し3Dオブジェクトを作成するSCNShapeクラスです。 2つの任意のポイントを接続する平面を取得するための適切な変換を実行すると、楽しい数学のように聞こえますが、一度実行すると、ポイントを任意の形状の線に簡単に接続できます。

14
rickster

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

    }
}
18
Jovan Stankovic

ここに1つの解決策があります

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

描画された線のプロパティの変更に関連する線幅または何かを更新したい場合は、SceneKitのレンダリングコールバックでopenGL呼び出しの1つを使用する必要があります。

func renderer(aRenderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
    //Makes the lines thicker
    glLineWidth(20)
}
9
TheCodingArt

以下の(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];
9
Matthew

これが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
}
2
silberz

したがって、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 c#については、そこにいます。

1
Andrew

ここでは、線の方向とは関係なく機能する三角形を使用したソリューションを示します。クロス積を使用して構築され、ラインに垂直なポイントが取得されます。そのため、小さな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)
    }
}
1
Jaykob