web-dev-qa-db-fra.com

Tracer une ligne entre deux points à l'aide de SceneKit

J'ai deux points (appelons-les pointA et pointB) de type SCNVector3. Je veux tracer une ligne entre eux. On dirait que cela devrait être facile, mais je ne peux pas trouver un moyen de le faire.

Je vois deux options, les deux ont des problèmes:

Utilisez un cylindre SCN avec un très petit rayon, avec la longueur | pointA-pointB | puis positionnez-le/faites-le pivoter.

Utilisez un SCNGeometry personnalisé mais ne savez pas comment; devrait définir deux triangles pour former un rectangle très mince peut-être?

Il semble qu'il devrait y avoir un moyen plus facile de faire cela, mais je n'arrive pas à en trouver un.

Edit: Utiliser la méthode du triangle me donne ceci pour tracer une ligne entre (0,0,0) et (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];

Mais il y a des problèmes: en raison des normales, vous ne pouvez voir cette ligne que d'un côté! C'est invisible de l'autre côté. De plus, si "delta" est trop petit, vous ne pouvez pas voir la ligne du tout. Dans l’état actuel des choses, il s’agit techniquement d’un rectangle, plutôt que de la ligne que je cherchais, ce qui pourrait donner lieu à de petits problèmes graphiques si je souhaite dessiner plusieurs lignes jointes.

18
Matthew

Il y a plusieurs manières de faire ça.

Comme indiqué précédemment, votre approche de géométrie personnalisée présente certains inconvénients. Vous devriez pouvoir corriger le problème de son invisible d'un côté en donnant à son matériel la propriété doubleSided . Vous pouvez toujours avoir des problèmes avec le fait qu'il soit bidimensionnel, cependant.

Vous pouvez également modifier votre géométrie personnalisée pour inclure davantage de triangles, afin d'obtenir une forme de tube à trois côtés ou plus au lieu d'un rectangle plat. Vous pouvez également avoir deux points dans votre source de géométrie et utiliser le type d'élément de géométrie SCNGeometryPrimitiveTypeLine pour que Scene Kit trace un segment de ligne entre eux. (Bien que vous n'obteniez pas autant de souplesse dans le rendu des styles avec le dessin au trait qu'avec les polygones ombrés.)

Vous pouvez également utiliser l'approche SCNCylinder que vous avez mentionnée (ou l'une des autres formes primitives intégrées). N'oubliez pas que les géométries sont définies dans leur propre espace de coordonnées local (modèle), que Scene Kit interprète par rapport à l'espace de coordonnées défini par un nœud. En d’autres termes, vous pouvez définir un cylindre (ou une boîte, une capsule, un plan ou autre) de 1,0 unité de large dans toutes les dimensions, puis utiliser la rotation/l’échelle/la position ou la transformation de la SCNNode contenant cette géométrie pour le rendre long, mince, etc. et s'étendant entre les deux points que vous voulez. (Notez également que, comme votre ligne va être très fine, vous pouvez réduire la segmentCounts de la géométrie intégrée que vous utilisez, car autant de détails ne seront pas visibles.)

Une autre option est la classe SCNShape qui vous permet de créer un objet 3D extrudé à partir d’un chemin Bézier 2D. Travailler sur la bonne transformation pour obtenir un plan reliant deux points arbitraires ressemble à une mathématique amusante, mais une fois que vous le faites, vous pouvez facilement connecter vos points à n’importe quelle forme de ligne que vous choisissez.

12
rickster

Voici une simple extension de 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])

    }
}
16
Jovan Stankovic

Nouveau code pour une ligne de (0, 0, 0) à (10, 10, 10) ci-dessous. Je ne sais pas si cela pourrait encore être amélioré.

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

Voici une solution

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

si vous souhaitez mettre à jour la largeur de ligne ou tout ce qui concerne la modification des propriétés de la ligne dessinée, vous voudrez utiliser l'un des appels openGL du rappel de rendu de SceneKit:

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

Ainsi, dans votre ViewController.cs, définissez vos points vectoriels et appelez une fonction Draw, puis sur la dernière ligne, il vous suffit de la faire pivoter pour regarder le point 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);

Créez une classe CreateGeomery statique et mettez-y cette méthode statique

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

vous voudrez peut-être aussi ces méthodes utilitaires dans une classe d'assistance statique

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

Pour tous mes amis Xamarin c # là-bas.

2
Andrew

Voici une solution utilisant des triangles fonctionnant indépendamment de la direction de la ligne. Elle est construite en utilisant le produit croisé pour obtenir des points perpendiculaires à la ligne. Vous aurez donc besoin d’une petite extension SCNVector3, mais elle sera probablement utile dans d’autres cas également.

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
}

Extension 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)
    }
}
0
Jaykob