Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get an ObservableFloatArray from a Stream?

This is nearly the same question as this one, but in the opposite direction.

I know there's no FloatStream in Java 8, and there're no many use cases for float[], but I have one:

Dealing with TriangleMesh in JavaFX 3D, you have to provide an ObservableFloatArray for the 3D coordinates of the vertices of the whole mesh.

As a result of some calculations I'll have all these coordinates in a List, and to add all of them at once to the mesh, I'll call triangleMesh.getPoints().addAll(), using one of the following methods:

  • addAll(ObservableFloatArray src)
  • addAll(float... elements)
  • addAll(ObservableFloatArray src, int srcIndex, int length)
  • addAll(float[] src, int srcIndex, int length)

where ObservableFloatArray can be created using FXCollections.observableFloatArray(), FXCollections.observableFloatArray(ObservableFloatArray array) or FXCollections.observableFloatArray(float... values).

Let's say I have this pojo for each vertex:

private class Vertex {
    private final float x;
    private final float y;
    private final float z;

    public Vertex(float x, float y, float z){
        this.x=x; this.y=y; this.z=z;
    }

    public float[] getCoordenates(){
        return new float[]{x,y,z};
    }
}

and after performing some calculations I have List<Vertex> listVertices. I'll need to generate float[] arrayVertices to finally call triangleMesh.getPoints().addAll(arrayVertices);.

For now this is what I'm doing:

listVertices.forEach(vertex->triangleMesh.getPoints().addAll(vertex.getCoordenates()));

But this triggers the associated listener on every new vertex added to the observable array, and for high number of vertices this affects performance.

Should FloatStream and flatMapToFloat() exist, I'd do something like this:

float[] arrayVertices = listVertices.stream()
            .map(vertex->FloatStream.of(vertex.getCoordenates()))
            .flatMapToFloat(f->f).toArray();
triangleMesh.getPoints().addAll(arrayVertices);

like I actually do with a list of int[] for face indices:

int[] arrayFaces = listFaces.stream()
            .map(face->IntStream.of(face.getFaceIndices()))
            .flatMapToInt(i->i).toArray();
triangleMesh.getFaces().addAll(arrayFaces);

But as far as I know, there's no way using streams.

Thanks in advance for any possible solution involving streams.

like image 480
José Pereda Avatar asked Nov 15 '14 22:11

José Pereda


1 Answers

Keep in mind that a Stream defines an operation rather that a storage. So for most operations, using a float provides only little benefit over double values when CPU registers are used. There might be a theoretical improvement for operations that could be accelerated using SSE or GPU, but that’s not relevant here.

So you can use a DoubleStream for that operation, the only thing you need is a collector capable of collecting a DoubleStream into a float[] array:

float[] arrayVertices = listVertices.stream()
    .flatMapToDouble(vertex->DoubleStream.of(vertex.x, vertex.y, vertex.z))
    .collect(FaCollector::new, FaCollector::add, FaCollector::join)
    .toArray();

static class FaCollector {
    float[] curr=new float[64];
    int size;
    void add(double d) {
        if(curr.length==size) curr=Arrays.copyOf(curr, size*2);
        curr[size++]=(float)d;
    }
    void join(FaCollector other) {
        if(size+other.size > curr.length)
            curr=Arrays.copyOf(curr, size+other.size);
        System.arraycopy(other.curr, 0, curr, size, other.size);
        size+=other.size;
    }
    float[] toArray() {
        if(size!=curr.length) curr=Arrays.copyOf(curr, size);
        return curr;
    }
}

This supports parallel processing, however, for an operation that merely consist of data copying only, there is no benefit from parallel processing.

like image 159
Holger Avatar answered Oct 04 '22 16:10

Holger