Highlight model part by touch

Started by pixelby, September 27, 2016, 05:12:22 PM

Previous topic - Next topic

pixelby

Hello. I'm absolutely new in JPCT (also as in 3dmodelling)  :-\

I'm faced with next problem - I have a model, and I need to catch user touches on it.

For example, I have a model "Car" with child elements - door, roof, bumper.
When user click on door on something else, I need to catch this, change color for this element, and get name of this element.

How to achieve this?

EgonOlsen

How to achieve what exactly? The picking, the highlighting...or both?

pixelby

#2
Hello. Sorry for my inaccuracy.

I mean picking. If I can identify child element which is selected by user, I can change color and determine this child name without problems, I think.

Thanks.


Also I faced with next problem.
My model consist of many child objects. I load model as I have shown below. After that I add to world every element from Object3D array . I'm dont merge them in one object for support picking each of them. But I need rotate my model as single object. How to grouping them without merging? Sorry if this question is very simple  :-[

private fun loadModel(filename: String, scale: Float): Array<Object3D> {
        val inputStream3ds = assets.open("truck_lowpoly.3ds")
        val model = Loader.load3DS(inputStream3ds, 4f)

        for (i in model.indices) {
            model.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS)
            model.setCollisionOptimization(Object3D.COLLISION_DETECTION_OPTIMIZED)
            model.build()
            model.strip()
        }
        return model
    }

EgonOlsen

Picking can be done this way: http://www.jpct.net/wiki/index.php?title=Picking#The_recommended_way

Just make sure that your touch coordinates are the ones that you think they are. The default behaviour is that the FrameBuffer starts at 0,0 beneath an existing title bar (if there is one) but the touch coordinates don't this into account, so that their origin is located below the title bar at the first visible pixel on the upper left. More info on that can be found here: http://www.jpct.net/forum2/index.php/topic,4181.msg29291.html

About grouping the models: The easiest way is to use parent/child relations between the objects. How you assign these is up to you, but one solution would be to add a dummy object. So you basically do something like this:


Object3D parent=Object3D.createDummyObj();
parent.build();
for(Object3D part:parts) {
   part.addParent(parent);
}


...and then transform the parent only to transform the whole group. The dummy object can be part of the World instance if you need that for some reason, but it doesn't have to.

pixelby

Thanks for you answers! This working great.  ;D

pixelby

#5
Quote from: EgonOlsen on September 28, 2016, 04:48:45 PM
Picking can be done this way: http://www.jpct.net/wiki/index.php?title=Picking#The_recommended_way

Just make sure that your touch coordinates are the ones that you think they are. The default behaviour is that the FrameBuffer starts at 0,0 beneath an existing title bar (if there is one) but the touch coordinates don't this into account, so that their origin is located below the title bar at the first visible pixel on the upper left. More info on that can be found here: http://www.jpct.net/forum2/index.php/topic,4181.msg29291.html

About grouping the models: The easiest way is to use parent/child relations between the objects. How you assign these is up to you, but one solution would be to add a dummy object. So you basically do something like this:


Object3D parent=Object3D.createDummyObj();
parent.build();
for(Object3D part:parts) {
   part.addParent(parent);
}


...and then transform the parent only to transform the whole group. The dummy object can be part of the World instance if you need that for some reason, but it doesn't have to.

Hello again. Now rotating working fine, I try to implement model parts picking.

This is my code for touch handling:
val pos = getWorldPosition()
if(pos != null) {
                    val res = world.calcMinDistanceAndObject3D(camera.getPosition, getWorldPosition(), 1000f)
                    if(res[1] != null) {
                        if(selectedObject != null) {

                        }
                        selectedObject = res[1] as Object3D
                        selectedObject!!.setTexture("red")
                        selectedObject!!.touch()
                        Log.v("Inspector", "CATCH COLLISION name " + selectedObject!!.name)
                    }
                }

    private fun getWorldPosition(): SimpleVector? {
        var pos: SimpleVector? = null
        val ray = Interact2D.reproject2D3DWS(world!!.getCamera(), fb, x, y)
        if (ray != null) {
            var norm = ray.normalize() // Just to be sure...

            val f = world!!.calcMinDistance(world!!.getCamera().position, norm, 1000f)
            if (f != Object3D.COLLISION_NONE) {
                val offset = SimpleVector(norm)
                norm.scalarMul(f)
                norm = norm.calcSub(offset)
                pos = SimpleVector(norm)
                pos.add(world!!.getCamera().position)
            }
        }
        return pos
    }

I'm faced with situation, when getWorldPosition return non-null pos, but Object3D [] returned by calcMinDistance have 1.0E12 as 0 element and null as 1 element. So, I cant detect object :(
Also, I'm remove toolbar on my activity. And one more moment: collision detects correctly in method getWorldPosition() (return pos only when I click on some model part), but calcMinDistance always return array[2] with null second value

Note : I'm using serialized models

EgonOlsen

I'm not sure what this code is supposed to do. calcMinDistanceAndObject3D() requires, just like calcMinDistance() a direction vector as the second parameter, but you are giving it a position vector of some kind. I don't think that you need all of that. Just do something like:


var pos: SimpleVector? = null
val ray = Interact2D.reproject2D3DWS(world!!.getCamera(), fb, x, y)
if (ray != null) {
    val res = world!!.calcMinDistanceAndObject3D(world!!.getCamera().position, ray.normalize() , 1000f)
    if(res[1] != null) {
       ....
    }
}

pixelby

Quote from: EgonOlsen on September 29, 2016, 11:22:50 PM
I'm not sure what this code is supposed to do. calcMinDistanceAndObject3D() requires, just like calcMinDistance() a direction vector as the second parameter, but you are giving it a position vector of some kind. I don't think that you need all of that. Just do something like:


var pos: SimpleVector? = null
val ray = Interact2D.reproject2D3DWS(world!!.getCamera(), fb, x, y)
if (ray != null) {
    val res = world!!.calcMinDistanceAndObject3D(world!!.getCamera().position, ray.normalize() , 1000f)
    if(res[1] != null) {
       ....
    }
}


Thank you very much! Now it works and I can pick my details!
:D

pixelby

Hello again.

Is it possible to get texture from Object 3D?

I need it for "unpicking" object - thats mean switch to "original" texture loaded from serialized model.

Now, when user clicking on model part, this part higlighted by red color, but when user pick another one, I need back to original texture of this part.

EgonOlsen

No, because it's stored internally in some different structure. Also, an Object3D can contain many textures in theory. If you need that, just write your own solution to keep track of your textures assignments, either by extending Object3D or by adding a simple helper class that manages textures.

pixelby

Quote from: EgonOlsen on October 06, 2016, 11:36:14 AM
No, because it's stored internally in some different structure. Also, an Object3D can contain many textures in theory. If you need that, just write your own solution to keep track of your textures assignments, either by extending Object3D or by adding a simple helper class that manages textures.

Good solution, but how I can get access to "default" texture loaded by deserializer? I try extends by Object3D but I cant get texture field, cuz its protected field. Also I know that texture manager store all my textures (created when deserializer load model), but all textures have unclear names like "__obj..." and I have no idea how to get model original texture by name from TextureManager

EgonOlsen

Yes, that's a problem...the Object3D doesn't store the texture name that it gets assigned and you can't override the method to store it, because it will be called by the Loader for an unextended Object3D instance...

There's a hacky solution for this: Use the PolygonManager to obtain the ID of the texture of the Object3D's first polygon (index 0). Then use that ID to obtain the texture from the TextureManager. If you absolutely have to, you could as well get the name that this texture has in the TextureManager by iterating over all names, get the corresponding texture and see if it's the same instance that you got returned via the ID....yes, it's ugly...but once done and hidden in a method, you can give it an important sounding name and forget about it... ;)

pixelby

#12
Quote from: EgonOlsen on October 11, 2016, 07:32:06 PM
Yes, that's a problem...the Object3D doesn't store the texture name that it gets assigned and you can't override the method to store it, because it will be called by the Loader for an unextended Object3D instance...

There's a hacky solution for this: Use the PolygonManager to obtain the ID of the texture of the Object3D's first polygon (index 0). Then use that ID to obtain the texture from the TextureManager. If you absolutely have to, you could as well get the name that this texture has in the TextureManager by iterating over all names, get the corresponding texture and see if it's the same instance that you got returned via the ID....yes, it's ugly...but once done and hidden in a method, you can give it an important sounding name and forget about it... ;)

Hi Egon. Thank for your's fast and helpful answers. This trick works almost perfect, but I faced with one more problem. Some objects contain several textures, my applyDefaultTexture method using your's solution, returns only one (first) of them. So, after "reverting", I have object with only "first" texture applied.

EgonOlsen

In that case, you either have to split the objects by texture in your editor or use the PolygonManager to obtain (and set) all of them on each single polygon.

pixelby

#14
Quote from: EgonOlsen on October 12, 2016, 02:01:34 PM
In that case, you either have to split the objects by texture in your editor or use the PolygonManager to obtain (and set) all of them on each single polygon.

I try to do this:
public Truck3dObject(Object3D obj) {
        super(obj);
        textureMap = new HashMap<>();
        final PolygonManager polygonManager = obj.getPolygonManager();
        int i = 0;
        try {
            while (polygonManager.getPolygonTexture(i) != -1) {
                textureMap.put(i, polygonManager.getPolygonTexture(i));
                i++;
            }
        } catch (RuntimeException e) {

        }
    }

    public void applyDefaultTexture() {
        Iterator it = textureMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pair = (Map.Entry) it.next();
            this.getPolygonManager().setPolygonTexture((Integer)pair.getKey(), (Integer) pair.getValue());
        }
    }
}

In constructor I save all object textures in hashmap, and in applyDefault try to restore it. But nothing happens, object still displays with "new" texture.

For "highlighting" object I use next method:

if(color == YELLOW) {
                model.setTexture("yellow")
            }

Edit: Found the issue.
this.getPolygonManager().setPolygonTexture((Integer)pair.getKey(), (Integer) pair.getValue()); not worked because I used model.strip().
Now it works!
Thank you, Egon!