Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update color of single point of GeometryModel3D Material rather than whole system of points

Tags:

c#

colors

wpf

I'm having trouble getting my head around the colour/material system of C# WPF projects, currently I am updating the colour of an entire system of points on each update of the model when I would instead like to just update the colour of a single point (as it is added).

AggregateSystem Class

public class AggregateSystem {
    // stack to store each particle in aggregate
    private readonly Stack<AggregateParticle> particle_stack;
    private readonly GeometryModel3D particle_model;
    // positions, indices and texture co-ordinates for particles
    private readonly Point3DCollection particle_positions;
    private readonly Int32Collection triangle_indices;
    private readonly PointCollection text_coords;
    // brush to apply to particle_model.Material
    private RadialGradientBrush rad_brush;
    // ellipse for rendering
    private Ellipse ellipse;
    private RenderTargetBitmap render_bitmap;

    public AggregateSystem() {
        particle_stack = new Stack<AggregateParticle>();
        particle_model = new GeometryModel3D { Geometry = new MeshGeometry3D() };
        ellipse = new Ellipse {
            Width = 32.0,
            Height = 32.0
        };
        rad_brush = new RadialGradientBrush();
        // fill ellipse interior using rad_brush
        ellipse.Fill = rad_brush;
        ellipse.Measure(new Size(32,32));
        ellipse.Arrange(new Rect(0,0,32,32));
        render_bitmap = new RenderTargetBitmap(32,32,96,96,PixelFormats.Pbgra32));
        ImageBrush img_brush = new ImageBrush(render_bitmap);
        DiffuseMaterial diff_mat = new DiffuseMaterial(img_brush);
        particle_model.Material = diff_mat;
        particle_positions = new Point3DCollection();
        triangle_indices = new Int32Collection();
        tex_coords = new PointCollection();
    }

    public Model3D AggregateModel => particle_model;

    public void Update() {
        // get the most recently added particle
        AggregateParticle p = particle_stack.Peek();
        // compute position index for triangle index generation
        int position_index = particle_stack.Count * 4;
        // create points associated with particle for circle generation
        Point3D p1 = new Point3D(p.position.X, p.position.Y, p.position.Z);
        Point3D p2 = new Point3D(p.position.X, p.position.Y + p.size, p.position.Z);
        Point3D p3 = new Point3D(p.position.X + p.size, p.position.Y + p.size, p.position.Z);
        Point3D p4 = new Point3D(p.position.X + p.size, p.position.Y, p.position.Z);
        // add points to particle positions collection
        particle_positions.Add(p1);
        particle_positions.Add(p2);
        particle_positions.Add(p3);
        particle_positions.Add(p4);
        // create points for texture co-ords
        Point t1 = new Point(0.0, 0.0);
        Point t2 = new Point(0.0, 1.0);
        Point t3 = new Point(1.0, 1.0);
        Point t4 = new Point(1.0, 0.0);
        // add texture co-ords points to texcoords collection
        tex_coords.Add(t1);
        tex_coords.Add(t2);
        tex_coords.Add(t3);
        tex_coords.Add(t4);
        // add position indices to indices collection
        triangle_indices.Add(position_index);
        triangle_indices.Add(position_index + 2);
        triangle_indices.Add(position_index + 1);
        triangle_indices.Add(position_index);
        triangle_indices.Add(position_index + 3);
        triangle_indices.Add(position_index + 2);
        // update colour of points - **NOTE: UPDATES ENTIRE POINT SYSTEM** 
        // -> want to just apply colour to single particles added
        rad_brush.GradientStops.Add(new GradientStop(p.colour, 0.0));
        render_bitmap.Render(ellipse);
        // set particle_model Geometry model properties
        ((MeshGeometry3D)particle_model.Geometry).Positions = particle_positions;
        ((MeshGeometry3D)particle_model.Geometry).TriangleIndices = triangle_indices;
        ((MeshGeometry3D)particle_model.Geometry).TextureCoordinates = tex_coords;
    }

    public void SpawnParticle(Point3D _pos, Color _col, double _size) {
        AggregateParticle agg_particle = new AggregateParticle {
            position = _pos, colour = _col, size = _size;
        }
        // push most-recently-added particle to stack
        particle_stack.Push(agg_particle);
    }

}

where AggregateParticle is a POD class consisting of Point3D position, Color color and double size fields which are self-explanatory.

Is there any simple and efficient method to update the colour of the single particle as it is added in the Update method rather than the entire system of particles? Or will I need to create a List (or similar data structure) of DiffuseMaterial instances for each and every particle in the system and apply brushes for the necessary colour to each?

[The latter is something I want to avoid at all costs, partly due to the fact it would require large structural changes to my code, and I am certain that there is a better way to approach this than that - i.e. there MUST be some simple way to apply colour to a set of texture co-ordinates, surely?!.]

Further Details

  • AggregateModel is a single Model3D instance corresponding to the field particle_model which is added to a Model3DGroup of the MainWindow.

  • I should note that what I am trying to achieve, specifically, here is a "gradient" of colours for each particle in an aggregate structure where a particle has a Color in a "temperature-gradient" (computed elsewhere in the program) which is dependent upon order in which it was generated - i.e. particles have a colder colour if generated earlier and a warmer colour if generated later. This colour list is pre-computed and passed to each particle in the Update method as can be seen above.

  • One solution I attempted involved creating a separate AggregateComponent instance for each particle where each of these objects has an associated Model3D and thus a corresponding brush. Then an AggregateComponentManager class was created which contained the List of each AggregateComponent. This solution works, however it is horrendously slow as each component has to be updated every time a particle is added so memory usage explodes - is there a way to adapt this where I can cache already rendered AggregateComponents without having to call their Update method each time a particle is added?

Full source code (C# code in the DLAProject directory) can be found on GitHub: https://github.com/SJR276/DLAProject

like image 561
sjrowlinson Avatar asked Jun 06 '16 22:06

sjrowlinson


1 Answers

We create WPF 3D models for smallish point clouds (+/- 100 k points) where each point is added as an octahedron (8 triangles) to a MeshGeometry3D.

To allow different colors for different points (we use this for selecting one or a subset of points) in such a point cloud, we assign texture coordinates from a small Bitmap.

At a high level we have some code like this:

BitmapSource bm = GetColorsBitmap(new List<Color> { BaseColor, SelectedColor });
ImageBrush ib = new ImageBrush(bm) 
{ 
    ViewportUnits = BrushMappingMode.Absolute, 
    Viewport = new Rect(0, 0, 1, 1) // Matches the pixels in the bitmap.
};  
GeometryModel3D model = new GeometryModel3D { Material = new DiffuseMaterial(ib) };

and now the texture coordinates are just

new Point(0, 0);
new Point(1, 0);

... etc.

The colors Bitmap comes from:

// Creates a bitmap that has a single row containing single pixels with the given colors.
// At most 256 colors.
public static BitmapSource GetColorsBitmap(IList<Color> colors)
{
    if (colors == null) throw new ArgumentNullException("colors");
    if (colors.Count > 256) throw new ArgumentOutOfRangeException("colors", "More than 256 colors");

    int size = colors.Count;
    for (int j = colors.Count; j < 256; j++)
    {
        colors.Add(Colors.White);
    }

    var palette = new BitmapPalette(colors);
    byte[] pixels = new byte[size];
    for (int i = 0; i < size; i++)
    {
        pixels[i] = (byte)i;
    }

    var bm = BitmapSource.Create(size, 1, 96, 96, PixelFormats.Indexed8, palette, pixels, 1 * size);
    bm.Freeze();
    return bm;
}

We also go to some effort to cache and reuse the internal Geometry structure when updating the point cloud.

Finally we display this with the awesome Helix Toolkit.

like image 53
Govert Avatar answered Oct 22 '22 23:10

Govert