SpeedBirdNine
SpeedBirdNine

Reputation: 4676

How to paint certain areas of a Model3DGroup in a different color and how to add Visual shapes on Model3DGroup

I can paint Model3DGroup in the color i want by using

Material material = new DiffuseMaterial(new SolidColorBrush(Colors.White));
GeometryModel3D model = new GeometryModel3D(mesh, material);

(following this tutorial)

to set the color of GeometryModel3D i include in the Model3DGroup.

I have an application where I have to place a map on a terrain in 3D (terrain is completely flat), the map is not an image, and I have detailed data of the points of the shapes I want to draw, and I also have DrawingVisual objects of all the shapes that I want to project on the 3D surface. In 2D mode, I draw them on a custom canvas (derived from Canvas) where I add them using

myCanvas.addVisualChild(item);
myCanvas.addLogicvalChild(item);

My question is how to "paint" or draw shapes and lines etc on 3D items? These shapes do not cover the terrain fully. I have tried using Viewport2DVisual3D class and tried placing a canvas on a 3D surface (a simple canvas with a button) using the following code:

Canvas testCanvas = new Canvas();
testCanvas.Background = Brushes.Green;
Button b1 = new Button();
b1.Content = "Hello there";
Canvas.SetTop(b1, 50);
Canvas.SetLeft(b1, 50);
Canvas.SetZIndex(b1, 2);
testCanvas.Children.Add(b1);

testVisual3d.Visual = testCanvas; // testVisual3d is a Viewport2DVisual3D declared in xaml

But the problem is that I am not able to figure out how the Canvas or any Visual "fills" the Viewport2DVisual3D class because:

  1. The button filled the canvas completely.
  2. Empty areas of the canvas (Canvas.SetTop(b1, 50)) is not visible.
  3. I have no idea idea about the size of the canvas related to the size of Viewport2DVisual3D object because the button always fills the object completely.

Also i cannot use Viewport2DVisual3D everywhere, as I also have to create models of buildings etc. when I am projecting the map to a 3D terrain, so I'll have to paint areas of a building model (which will be a Model3DGroup) differently to give a realistic effects, but if i manage to project the map on a Viewport2DVisual3D, it'll solve lots of problems as i'll be able to directly project all the shapes including grid on the Viewport2DVisual3D object or terrain.

I am using .NET 4.0 and C#.

Please help.

Update

Using this code solves the initial problem of canvas size and space:

Canvas testCanvas = new Canvas();
testCanvas.Background = Brushes.Green;
Button b1 = new Button();
b1.Width = 120;
b1.Height = 25;

testCanvas.Width = 200;
testCanvas.Height = 200;

b1.Content = "Hello there";
Canvas.SetTop(b1, 50);
Canvas.SetLeft(b1, 50);
Canvas.SetZIndex(b1, 2);
testCanvas.Children.Add(b1);

testVisual3d.Visual = testCanvas;

The size of Viewport2DVisual3D is

Positions="0,0,0 0,0,30 30,0,30 30,0,0"

And the canvas resizes to fit the boundaries of the Viewport2DVisual3D, but will it work with a class derived from Canvas in which shapes have directly been added using Canvas.AddVisualChild and Canvas.AddLogicalChild, i am yet to try that.

And also the original question of painting on the Model3DGroup remains, how to do it?

Upvotes: 1

Views: 4157

Answers (3)

Solveering
Solveering

Reputation: 31

So here's some code that I've used (with some modifications, adapt as necessary). This is in VB, porting to C# should be easy. Note that this is a copy-paste from some existing project, so some items may not be as clear as they could be... sorry for being lazy...

Private Sub RefreshRHDisplay()
        'global/input data is RHTris which is a List(of Point3D())
        'updates the data on the display with the available data
        Dim v, f, i As Integer

        'clear geometry
        Group3D_RH.Children.Clear() 'is a Model3DGroup object

        'define lights
        Dim _L As Model3DGroup = GetLighting() 'defines the lighting

        'mesh items
        Dim mesh As New MeshGeometry3D

        'group of items
        Dim geomGroup As New Model3DGroup

        'materials
        Dim mat As MaterialGroup  'the foreground/front material
        Dim bmat As MaterialGroup 'the background/back material

        Dim MatGroup As New MaterialGroup
        Dim ColBrush As Brush = New SolidColorBrush(_bodyCol) : ColBrush.Opacity = 1.0F
        Dim LightBrush As Brush = New SolidColorBrush(Colors.White) : LightBrush.Opacity = 0.3
        Dim _diffuseMaterial As New DiffuseMaterial(ColBrush)
        Dim _specularMaterial As New SpecularMaterial(LightBrush, 300.0F)
        MatGroup.Children.Add(_diffuseMaterial)
        MatGroup.Children.Add(_specularMaterial)

        mat = MatGroup

        MatGroup = New MaterialGroup
        Dim bColBrush As Brush = New SolidColorBrush(Colors.DarkGray) : bColBrush.Opacity = 1.0F
        _diffuseMaterial = New DiffuseMaterial(bColBrush)
        MatGroup.Children.Add(_diffuseMaterial)
        MatGroup.Children.Add(_specularMaterial)

        bmat = MatGroup

        Dim vPts(0) As Point

        'distinguish between direct (uniform color) and colorize mode
        If chkColorize.IsChecked Then 'COLORIZE MODE

'Note: the gradient stop ends at 0.9, this is purely so that I can assign a special color after that.
            Dim gsp As New GradientStopCollection({New GradientStop(Colors.Red, 0.0),
                                                   New GradientStop(Colors.Orange, 0.15),
                                                   New GradientStop(Colors.Yellow, 0.3),
                                                  New GradientStop(Colors.Green, 0.45),
                                                  New GradientStop(Colors.Blue, 0.6),
                                                  New GradientStop(Colors.Indigo, 0.75),
                                                   New GradientStop(Colors.Violet, 0.9)
                                                  })
            Dim lBrsh As New LinearGradientBrush(gsp, New Point(0, 1), New Point(0.9, 1))

            'define random locations for the textures
            ReDim vPts(RHTris.Count - 1)
            Dim rnd As New Random(CInt(Now.Ticks Mod Integer.MaxValue))
            For v = 0 To RHTris.Count - 1
                vPts(v) = New Point(rnd.NextDouble, 1)
            Next

            'replace the default material with the linear gradient
            mat.Children.Clear()
            mat.Children.Add(New DiffuseMaterial(lBrsh))


        End If

        'add all vertices
        For v = 0 To RHTris.Count - 1
            mesh.Positions.Add(RHTris.Item(v)(0))
            mesh.Positions.Add(RHTris.Item(v)(1))
            mesh.Positions.Add(RHTris.Item(v)(2))
        Next
        'add all triangles
        v = 0
        For f = 0 To RHTris.Count - 1
            'If .isVisible Then 'true by def of _visList
            Dim v0, v1, n As Vector3D
            v0 = Point3D.Subtract(RHTris.Item(f)(1), RHTris.Item(f)(0))
            v1 = Point3D.Subtract(RHTris.Item(f)(2), RHTris.Item(f)(0))
            n = Vector3D.CrossProduct(v0, v1)
            mesh.Normals.Add(n)
            mesh.Normals.Add(n)
            mesh.Normals.Add(n)
            mesh.TriangleIndices.Add(v)
            mesh.TriangleIndices.Add(v + 1)
            mesh.TriangleIndices.Add(v + 2)
            v += 3
        Next

        If chkColorize.IsChecked Then 'define the texture coordinates that define the color through the linear gradient brush
            For v = 0 To RHTris.Count - 1
                For i = 0 To 2
                    mesh.TextureCoordinates.Add(vPts(v)) 'add same three points for each location
                Next
            Next
        End If

        'body mesh has been defined, create body and add to visual
        Dim geo As New GeometryModel3D
        geo.Geometry = mesh
        geo.Material = mat
        geo.BackMaterial = bmat


        'combine to group
        geomGroup.Children.Add(geo)

        'add to scene
        Group3D_RH.Children.Add(geomGroup)

        'add lights back in
        Group3D_RH.Children.Add(_L)

    End Sub

Some basic comments are in order: the data is in the RHTris collection which is a variable of type List(of Point3D()) which contains three locations. The color assigned to each is either a default material or a random color (if chkColorize is checked). The Sub also applies the data to the Group3D_RH which is a Model3DGroup object. The approach uses either a solid material or a (constant) texture that has a color with a gradient (going through all the colors that are then available). The assignment of color is then to define a point (2D point) to the texture coordinates. If each point of the triangle has the same point assigned then the triangle ends up having a uniform color (based on its coordinates along the color gradient)... Hope this at least illustrates the approach...

Upvotes: 1

Solveering
Solveering

Reputation: 31

This may be a bit late, but I was having issues with a very similar problem. Though the above works fine for smaller meshes, once you get into the hundreds and thousands (or more) of triangles, it becomes slow. The alternative I have implemented uses a uniform material for all meshes/collections but uses a lineargradientbrush to assign a gradient color to the material (stops and colors set as needed for what you are trying to achieve). Then by using texture coordinates, the color of a given triangle can be set (all three texture coordinates would be the same point if you want to have a solid color). This approach works far better and is more efficient for large meshes (10k and up) with only extra time required to set up the brush and assigning texture coordinates. The same/similar approach can also be used for changing the color of a triangle through hit-testing or similar whereby only the triangle's vertices (index) are provided and the source mesh (thus the index of the texture coordinates that need updating are provided)

Upvotes: 2

SpeedBirdNine
SpeedBirdNine

Reputation: 4676

After a bit of experimenting, i have the answer, simple way is to set the color of each triangle while defining the mesh. In WPF, what is needed is to set the Material color when combining the triangle (MeshGeometry3D) and material while defining a new GeometryModel3D.

(from this tutorial)

Material material = new DiffuseMaterial(
        new SolidColorBrush(Colors.DarkKhaki)); //Set color here
    GeometryModel3D model = new GeometryModel3D(
        mesh, material);

I am a beginner of 3D and I have not yet explored the concept of materials.

If someone knows another way, please post here.

Upvotes: 0

Related Questions