How to wrap sphere with Image?

Emon Haque 3,176 Reputation points
2021-04-15T22:28:03.773+00:00

I wanted to wrap the sphere with this JPEG (mercator map).

88393-test1.gif

So the primary problem, obviously, is the TextureCoordinates. Here's the code:

class Plot3D : FrameworkElement  
{  
    Viewport3D viewPort;  
    PerspectiveCamera camera;  
    Model3DGroup models;  
    Point3DAnimationUsingKeyFrames positionAnim;  
    Vector3DAnimationUsingKeyFrames lookAnim;  

    public Plot3D() {  
        camera = new PerspectiveCamera() {  
            FieldOfView = 80,  
            LookDirection = new Vector3D(0, 0, 9),  
            Position = new Point3D(0, 0, -9),  
            UpDirection = new Vector3D(0, 1, 0)  
        };  
        models = new Model3DGroup() { Children = { new AmbientLight(Colors.White) } };  
        viewPort = new Viewport3D() {  
            Camera = camera,  
            Children = { new ModelVisual3D() { Content = models } }  
        };  
        AddVisualChild(viewPort);  
        addModel();  

        positionAnim = new Point3DAnimationUsingKeyFrames() {  
            KeyFrames = {  
                new LinearPoint3DKeyFrame(new Point3D( 0, 0,-9), TimeSpan.FromSeconds(0)),  
                new LinearPoint3DKeyFrame(new Point3D(-9, 0, 0), TimeSpan.FromSeconds(5)),  
                new LinearPoint3DKeyFrame(new Point3D( 0, 0, 9), TimeSpan.FromSeconds(10)),  
                new LinearPoint3DKeyFrame(new Point3D( 9, 0, 0), TimeSpan.FromSeconds(15)),  
                new LinearPoint3DKeyFrame(new Point3D( 0, 0,-9), TimeSpan.FromSeconds(20))  
            },  
            RepeatBehavior = RepeatBehavior.Forever  
        };  
        lookAnim = new Vector3DAnimationUsingKeyFrames() {  
            KeyFrames = {  
                new LinearVector3DKeyFrame(new Vector3D( 0, 0, 9), TimeSpan.FromSeconds(0)),  
                new LinearVector3DKeyFrame(new Vector3D( 9, 0, 0), TimeSpan.FromSeconds(5)),  
                new LinearVector3DKeyFrame(new Vector3D( 0, 0,-9), TimeSpan.FromSeconds(10)),  
                new LinearVector3DKeyFrame(new Vector3D(-9, 0, 0), TimeSpan.FromSeconds(15)),  
                new LinearVector3DKeyFrame(new Vector3D( 0, 0, 9), TimeSpan.FromSeconds(20))  
            },  
            RepeatBehavior = RepeatBehavior.Forever  
        };  
        Loaded += animate;  
    }  

    void animate(object sender, RoutedEventArgs e) {  
        camera.BeginAnimation(PerspectiveCamera.PositionProperty, positionAnim);  
        camera.BeginAnimation(PerspectiveCamera.LookDirectionProperty, lookAnim);  
    }  

    void addModel() {  
        var mesh = new MeshGeometry3D();  
        double radius = 2;  
        double dTheta, dPhi;  
        dTheta = dPhi = 0.2;  
        for (double theta = 0; theta < 2 * Math.PI; theta += dTheta) {  
            for (double phi = 0; phi < 2 * Math.PI; phi += dPhi) {  
                var p1 = sphericalPoint(radius, theta, phi);  
                var p2 = sphericalPoint(radius, theta + dTheta, phi);  
                var p3 = sphericalPoint(radius, theta + dTheta, phi + dPhi);  
                var p4 = sphericalPoint(radius, theta, phi + dPhi);  
                addTriangle(mesh, p1, p2, p3);  
                addTriangle(mesh, p3, p4, p1);  
                addWires(p1, p2, p3, p4);  
                mesh.TextureCoordinates.Add(new Point(p1.X, p1.Z));  
                mesh.TextureCoordinates.Add(new Point(p2.X, p2.Z));  
                mesh.TextureCoordinates.Add(new Point(p3.X, p3.Z));  
                mesh.TextureCoordinates.Add(new Point(p4.X, p4.Z));  
            }  
        }  
        var source = new BitmapImage(new Uri("Resource/map.jpg", UriKind.Relative));  
        var sphereSurface = new GeometryModel3D() {  
            Geometry = mesh,  
            Material = new DiffuseMaterial(new ImageBrush() {  
                ImageSource = source,  
                TileMode = TileMode.None,  
                Stretch = Stretch.Uniform  
            })  
        };  
        models.Children.Add(sphereSurface);  
    }  

    Point3D sphericalPoint(double radius, double theta, double phi) {  
        return new Point3D() {  
            X = radius * Math.Sin(theta) * Math.Cos(phi),  
            Y = radius * Math.Cos(theta),  
            Z = -radius * Math.Sin(theta) * Math.Sin(phi)  
        };  
    }  

    void addWires(Point3D p1, Point3D p2, Point3D p3, Point3D p4) {  
        var line = new ScreenSpaceLines3D() { Color = Colors.Gray, Thickness = 1 };  
        line.Points.Add(p1);  
        line.Points.Add(p2);  
        line.Points.Add(p2);  
        line.Points.Add(p3);  
        line.Points.Add(p3);  
        line.Points.Add(p4);  
        line.Points.Add(p4);  
        line.Points.Add(p1);  
        viewPort.Children.Add(line);  
    }  

    void addTriangle(MeshGeometry3D mesh, Point3D p1, Point3D p2, Point3D p3) {  
        int index1 = addPoint(mesh.Positions, p1);  
        int index2 = addPoint(mesh.Positions, p2);  
        int index3 = addPoint(mesh.Positions, p3);  
        mesh.TriangleIndices.Add(index1);  
        mesh.TriangleIndices.Add(index2);  
        mesh.TriangleIndices.Add(index3);  
    }  

    int addPoint(Point3DCollection positions, Point3D point) {  
        for (int i = 0; i < positions.Count; i++) {  
            if ((point.X == positions[i].X) &&  
                (point.Y == positions[i].Y) &&  
                (point.Z == positions[i].Z))  
                return i;  
        }  
        positions.Add(point);  
        return positions.Count - 1;  
    }  

    protected override Size ArrangeOverride(Size finalSize) {  
        viewPort.Width = finalSize.Width;  
        viewPort.Height = finalSize.Height;  
        viewPort.Measure(finalSize);  
        viewPort.Arrange(new Rect(viewPort.DesiredSize));  
        return finalSize;  
    }  

    protected override Visual GetVisualChild(int index) => viewPort;  
    protected override int VisualChildrenCount => 1;  
}  
Developer technologies Windows Presentation Foundation
0 comments No comments
{count} votes

Accepted answer
  1. DaisyTian-1203 11,646 Reputation points
    2021-04-16T04:19:19.69+00:00

    I update your addModel() as below:

      private void addModel(Model3DGroup model_group)  
            {  
                Model3DGroup globe_model = new Model3DGroup();  
                model_group.Children.Add(globe_model);  
      
                ImageBrush globe_brush = new ImageBrush(new BitmapImage(new Uri("Resource/map.jpg", UriKind.Relative)));  
                Material globe_material = new DiffuseMaterial(globe_brush);  
                MeshGeometry3D globe_mesh = null;  
                MakeSphere(globe_model, ref globe_mesh, globe_material, 1, 0, 0, 0, 20, 30);  
            }  
      
      
            private void MakeSphere(Model3DGroup model_group, ref MeshGeometry3D sphere_mesh, Material sphere_material,  
              double radius, double cx, double cy, double cz, int num_phi, int num_theta)  
            {  
                if (sphere_mesh == null)  
                {  
                    sphere_mesh = new MeshGeometry3D();  
                    GeometryModel3D new_model = new GeometryModel3D(sphere_mesh, sphere_material);  
                    model_group.Children.Add(new_model);  
                }  
      
                double dphi = Math.PI / num_phi;  
                double dtheta = 2 * Math.PI / num_theta;  
      
                int pt0 = sphere_mesh.Positions.Count;  
      
                double phi1 = Math.PI / 2;  
                for (int p = 0; p <= num_phi; p++)  
                {  
                    double r1 = radius * Math.Cos(phi1);  
                    double y1 = radius * Math.Sin(phi1);  
      
                    double theta = 0;  
                    for (int t = 0; t <= num_theta; t++)  
                    {  
                        sphere_mesh.Positions.Add(new Point3D(  
                            cx + r1 * Math.Cos(theta), cy + y1, cz + -r1 * Math.Sin(theta)));  
                        sphere_mesh.TextureCoordinates.Add(new Point(  
                            (double)t / num_theta, (double)p / num_phi));  
                        theta += dtheta;  
                    }  
                    phi1 -= dphi;  
                }  
      
                int i1, i2, i3, i4;  
                for (int p = 0; p <= num_phi - 1; p++)  
                {  
                    i1 = p * (num_theta + 1);  
                    i2 = i1 + (num_theta + 1);  
                    for (int t = 0; t <= num_theta - 1; t++)  
                    {  
                        i3 = i1 + 1;  
                        i4 = i2 + 1;  
                        sphere_mesh.TriangleIndices.Add(pt0 + i1);  
                        sphere_mesh.TriangleIndices.Add(pt0 + i2);  
                        sphere_mesh.TriangleIndices.Add(pt0 + i4);  
      
                        sphere_mesh.TriangleIndices.Add(pt0 + i1);  
                        sphere_mesh.TriangleIndices.Add(pt0 + i4);  
                        sphere_mesh.TriangleIndices.Add(pt0 + i3);  
                        i1 += 1;  
                        i2 += 1;  
                    }  
                }  
            }  
    

    The result picture is:
    88493-2.gif


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Emon Haque 3,176 Reputation points
    2021-04-16T19:33:15.563+00:00

    With the rectangular approach (creating 2 triangles out of the the 4 points) It makes two white strips. One in between the start and end of image (In pacific ocean, where Time zone shifts?) and the other left to the first one:

    88692-1.png

    The first glitch is common with either approach. For rectangular approach I'd to keep track of currentPhi and currentTheta in the addModel() function like this:

    double currentPhi, currentTheta;  
    void addModel() {  
        var mesh = new MeshGeometry3D();  
        double dTheta, dPhi, radius;  
        radius = 4;  
        dTheta = dPhi = 0.25;  
        for (double theta = 0; theta < Math.PI; theta += dTheta) {  
            currentTheta = theta;  
            for (double phi = 0; phi < 2 * Math.PI; phi += dPhi) {  
                currentPhi = phi;  
                var p1 = sphericalPoint(radius, theta, phi);  
                var p2 = sphericalPoint(radius, theta + dTheta, phi);  
                var p3 = sphericalPoint(radius, theta + dTheta, phi + dPhi);  
                var p4 = sphericalPoint(radius, theta, phi + dPhi);  
                addTriangle(mesh, p1, p2, p3);  
                addTriangle(mesh, p3, p4, p1);  
            }  
        }  
        var sphere = new GeometryModel3D() {  
            Geometry = mesh,  
            Material = new DiffuseMaterial(new ImageBrush() {  
                ImageSource = new BitmapImage(new Uri("Resource/map.jpg", UriKind.Relative))  
            })  
        };  
        models.Children.Add(sphere);  
    }  
    

    and use those currentPhi and currentTheta in addPoint() method like this:

    int addPoint(MeshGeometry3D mesh, Point3D point) {  
        for (int i = 0; i < mesh.Positions.Count; i++) {  
            if ((point.X == mesh.Positions[i].X) &&  
                (point.Y == mesh.Positions[i].Y) &&  
                (point.Z == mesh.Positions[i].Z))  
                return i;  
        }  
        mesh.Positions.Add(point);  
        mesh.TextureCoordinates.Add(new Point(currentPhi / (2 * Math.PI) , currentTheta / Math.PI));  
        return mesh.Positions.Count - 1;  
    }  
    

    to generate TextureCoordinates. With this rectangular approach it actually generates far less points for Positions, TriangleIndices and TextureCoordinates. Another problem is with wireframe. If I use the following code in addModel() function:

    void addModel() {  
        double dTheta, dPhi, radius;  
        radius = 4;  
        dTheta = dPhi = 0.25;  
        for (double theta = 0; theta < Math.PI; theta += dTheta) {  
            for (double phi = 0; phi < Math.PI; phi += dPhi) {  
                var p1 = sphericalPoint(radius, theta, phi);  
                var p2 = sphericalPoint(radius, theta + dTheta, phi);  
                var p3 = sphericalPoint(radius, theta + dTheta, phi + dPhi);  
                var p4 = sphericalPoint(radius, theta, phi + dPhi);  
                addWires(p1, p2, p3, p4);  
            }  
        }  
    }  
    

    I see this image, after some camera movement:

    88712-2.png

    So it basically generates some sort of protractor at the bottom. With phi < 2 * Math.PI in the nested for loop, I see two extra longitudes:

    88670-3.png

    If I could get rid of these glitches, I'd go with this.

    EDIT
    ----
    Degree instead of Radian actually solves the protractor at the bottom and extra longitude issues, who knows why! With int currentPhi, currentTheta; and int dTheta = dPhi = 10, it's perfect:

    88714-4.png

    The only problem is the TextureCoordinates, that glitch (white strip) remains where both end of the image wants to meet.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.