Geometry Shader?

Nope. Z sorting happens at the geometry level in the transparent bucket. All the GPU will do is zbuffer occlusion.

Yes, I mean create your own batch with a custom mesh. JME’s batching would be ridiculous to use in this case because you’d have to have full geometry and mesh per blade of grass… versus just putting those all in one big buffer yourself which isn’t very hard.

ok, but how do you deal with the position where you put the gras clumps ?
ray tracing is to slow, …so far I thought about doing ray tracing once and caching the intersection points with the ground in a paging system/file

or I solve y for x,z on each ground triangle plane (based on a grid)…

In my case, i generated a dense grid of gras, and sampled the heightmap to set the correct possition. Does only work for heightmap based terrain of course.

Upside is, i can move the grid around for free and don’t have to regenerate/modify any meshes

In my case, I create all of the grass positions first and look at the ground mesh to see if I can really put them there and what height they should be.

In any case, create the grass points (Vector3f should be fine) once and just keep them around… sort this array based on the dot-product with the view direction. Then update the mesh with the latest locations.

getting ~240fps with this method on my 4 year old card. That’ll do for me… :smile:

1 Like

yeah I guess I’ll go for this option
I already created a template Octree class so I can page props and grass positions on demand

You probably really mean a fixed grid. I doubt an octree buys you anything but a ton of complexity and worse performance.

Here is how the conversation would usually go to short circuit some things:
Other person: “No, I really use an octree”
me: “So you split infinitely until every object is in its own node?”
Other person: “No, I stop at a fixed minimum size.”
me: “So why not just build a grid at the fix minimum size and get O(1) performance instead of O(n log n) or whatever?”

well I guess one use tree cos it is supposed go fast when adressing it

but, yes I made a 3D grid with fixed cell size , so far I check each object bounds and populate the grid cells
then I’ll have to find out witch cells intersect with the camera frustum…

And that’s the myth/trap.

A tree traversal is always (always) going to be slower than x / cellSize, z / cellSize.
log time versus constant time.

The case where a tree works better than a sparse fixed grid is very rare and usually involves compression where the tree structure is actually eliminating whole sections of the world… even then I might argue for a multi-level sparse fixed grid. (Sparse meaning that you only keep the cells around that have things in them (hashmap) versus creating a giant array.)

indeed

although I use (nested) hasmaps to store data, it might be quiet complex to retreive data from these, I dont know

<HashMap<Integer,<HashMap<Integer,<HashMap<Integer,GridCell>>> where the int indexes are xyz cell “coordinates”, maybe I should go for a simpler hashmap with String keys like x+":"+y+":"+z …?!

Don’t use strings. Make your own key class. It will be way faster… or even a Vector3i class that properly implements hashCode and equals then you can use that as a key.

Something like:

/*
 * $Id$
 * 
 * Copyright (c) 2015, Simsilica, LLC
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions 
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in 
 *    the documentation and/or other materials provided with the 
 *    distribution.
 * 
 * 3. Neither the name of the copyright holder nor the names of its 
 *    contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package espace.mathd;

import com.jme3.math.Vector3f;

/**
 *  Holds a three-dimensional integer coordinate.
 *
 *  @author    Paul Speed
 */
public class Vec3i implements Cloneable {

    public int x;
    public int y;
    public int z;

    public Vec3i() {
    }

    public Vec3i( int x, int y, int z ) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    
    public Vec3i( Vector3f v ) {
        this.x = (int)v.x;
        this.y = (int)v.y;
        this.z = (int)v.z;
    }

    public Vec3i( Vec3i v ) {
        this.x = v.x;
        this.y = v.y;
        this.z = v.z;
    }

    public Vector3f toVector3f() {
        return new Vector3f(x,y,z);
    }

    @Override
    public boolean equals( Object o ) {   
        if( o == this )
            return true;
        if( o == null )
            return false;
        if( o.getClass() != getClass() )
            return false;
        Vec3i other = (Vec3i)o;
        return x == other.x && y == other.y && z == other.z;
    }
    
    @Override
    public int hashCode() {
        int hash = 37;
        hash += 37 * hash + x;
        hash += 37 * hash + y;
        hash += 37 * hash + z;
        return hash;
    }

    @Override
    public Vec3i clone() {
        try {
            return (Vec3i)super.clone();
        } catch( CloneNotSupportedException e ) {
            throw new RuntimeException( "Error cloning", e );
        }
    }

    public final void set( int x, int y, int z ) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public final void set( Vec3i val ) {
        this.x = val.x;
        this.y = val.y;
        this.z = val.z;
    }

    public final int get( int index ) {
        switch( index ) {
            case 0:
                return x;
            case 1:
                return y;
            case 2:
                return z;
            default:
                throw new IndexOutOfBoundsException( String.valueOf(index) );
        }
    }

    public final void set( int index, int value ) {
        switch( index ) {
            case 0:
                this.x = value;
                break;
            case 1:
                this.y = value;
                break;
            case 2:
                this.z = value;
                break;
            default:
                throw new IndexOutOfBoundsException( String.valueOf(index) );
        }
    }

    public final void addLocal( Vec3i v ) {
        x += v.x;
        y += v.y;
        z += v.z;
    }

    public final void addLocal( int i, int j, int k ) {
        x += i;
        y += j;
        z += k;
    }

    public final void subtractLocal( Vec3i v ) {
        x -= v.x;
        y -= v.y;
        z -= v.z;
    }

    public final void subtractLocal( int i, int j, int k ) {
        x -= i;
        y -= j;
        z -= k;
    }
    
    public final Vec3i add( int i, int j, int k ) {
        return new Vec3i( x + i, y + j, z + k );
    }

    public final Vec3i add( Vec3i v ) {
        return new Vec3i( x + v.x, y + v.y, z + v.z );
    }
 
    public final Vec3i subtract( Vec3i v ) {
        return new Vec3i( x - v.x, y - v.y, z - v.z );
    } 
    
    public final Vec3i mult( int scale ) {
        return new Vec3i( x * scale, y * scale, z * scale );
    }
    
    public final int getDistanceSq( Vec3i v ) {
        int xd = v.x - x;
        int yd = v.y - y;
        int zd = v.z - z;
        
        return xd * xd + yd * yd + zd * zd;
    }
      
    public final double getDistance( Vec3i v ) {
        return Math.sqrt(getDistanceSq(v));
    }  

    public void minLocal( int i, int j, int k ) {
        x = Math.min(x, i);
        y = Math.min(y, j);
        z = Math.min(z, k);
    }
    
    public void maxLocal( int i, int j, int k ) {
        x = Math.max(x, i);
        y = Math.max(y, j);
        z = Math.max(z, k);
    }

    public void minLocal( Vec3i v ) {
        x = Math.min(v.x, x);
        y = Math.min(v.y, y);
        z = Math.min(v.z, z);
    }
    
    public void maxLocal( Vec3i v ) {
        x = Math.max(v.x, x);
        y = Math.max(v.y, y);
        z = Math.max(v.z, z);
    }

    @Override
    public String toString() {
        return "Vec3i[" + x + ", " + y + ", " + z + "]";
    }
}

Edit: I have no idea why the forum screwed up the indents.

something like this would do it ?

    public class GridPagerKey
    {
        int hash=0;
        
        public GridPagerKey(int x,int y,int z)
        {
            hash = 37;
            hash += 37 * hash + x;
            hash += 37 * hash + y;
            hash += 37 * hash + z;
        }
        
        @Override
        public int hashCode() 
        {
            return hash;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final GridPagerKey other = (GridPagerKey) obj;
            if (this.hash != other.hash) {
                return false;
            }
            return true;
        }
        
    }

netbeans proposed me to create the equals function, but is it necessary ?

You’ve misunderstood how hash codes and hashmaps work. You need hashCode() AND equals()… and equals must be against the original values. With your implementation, you will get some locations that end up resolving to the same hash and clobbering each other.

Just take the Vector3i class and use it.

why ?

I just precomputed the hashcode in the constructor… for faster access
the hashcode generated will be equal for same x,y,z values and different for other cases

I modified it like this

    public class GridPagerKey // to have proper hashcode/equals comparison functions for hashmap
    {
        private int hash;
        private final int x;
        private final int y;
        private final int z;        
        
        public GridPagerKey(int x,int y,int z)
        {
            this.x=x;
            this.y=y;
            this.z=z;
            
            hash = 37;
            hash += 37 * hash + x;
            hash += 37 * hash + y;
            hash += 37 * hash + z;
        }
        
        @Override
        public int hashCode() 
        {
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final GridPagerKey other = (GridPagerKey) obj;
            if (this.x != other.x) {
                return false;
            }
            if (this.y != other.y) {
                return false;
            }
            if (this.z != other.z) {
                return false;
            }
            return true;
        }
        
    }

I just try to understand the essence of hasMap/equals/hashCode

Yep, that’s better. Your equals() method was wrong before because it would potentially say two keys were equal even if they had different x, y, z. Hash codes are not unique. Two different x,y,z values can create the same hashcode so you must compare the x, y, z separately as you do now.

…though if you are going to cache the hashcode then you could check it first. If one is not equal to another then you know the objects aren’t equal. By contract, if obj1.equals(obj2) then their hash codes must also be equal.

but how hash tables manage data when hashCode() could give same hascode with different x,y,z values ?

guess it is like md5, if by any chance you create a md5 that is already in the table, you create another one or something

Exactly with that equals() method.

You should read about how hashtables work, really.

The short answer is that the hashcode determines the bucket but you may have many objects in a bucket (hash collisions). .equals() is used to find an object in a bucket. And anyway, there are never so many buckets that each hashcode gets one. Buckets are defined based on the current hash radix as defined by the size of the bucket list.

…really I’m doing a poor job of explaining what the web probably explains better 5,000,000 times.

dont worry I appreciate your patience and explanations