Polygon Intersection for buoyancy

edited May 2013 in Help request
I was wanting to simulate buoyancy, as seen on this page.

I did a quick source search, and I found a lot of AABB intersection functions, but didn't see anything that does polygon intersection.

No problems with [strike]rolling my own[/strike] stealing code, just don't want to reinvent the wheel.

Comments

  • edited May 2013
    Why not using the algorithm suggested in the article you linked?

    As for the ComputeCentroid function, it could be simplified a bit given that pRef is always (0, 0).
  • edited May 2013
    Btw, if you only have a single plane for water, the clipping algorithm can be simplified quite a lot:

    - For all points in your polygon
    - Look on which side of your water line it is
    -> on the correct side (ie. underwater), then add it to your result
    -> on the other side, find the intersection of the polygon segment (defined by previous point and the current tested point) and the water line and add it to your polygon result

    There's a special case where the first point you test is out of water, in which case you'll need to add the intersection point the first time you get underwater.
    There are many ways of handling this, one option would be to iterate first through all the points in your poly till you find one underwater, and when you're done clipping, if you didn't start with the first point underwater, then add the intersection of the segment formed by the point with which you started and its previous on with the water line, and add it as the last vertex of your polygon.

    Sorry if all his doesn't sound very clear, if need be I can provide you with some code, but not sure when. :)
  • edited June 2013
    Iarwain,

    Thanks for your timely and helpful reply.

    This leads to a new question: How do I get the vertices of a body part from within an events handler.

    I see that orxBODY has an orxBODY_GetNextPart function, but when I attempt to get an orxBODY * using orxBODY(), I only ever get null.

    Not sure how orxBODY is different/same as orxPHYSICS_BODY.

    Below, everything in side the if-block marked 'experimental' is what I'm trying to make work. Everything below that is currently working for my current crappy approach to buoyancy.
    orxSTATUS orxFASTCALL Game::physicsEventHandler(const orxEVENT *_pstEvent) {
    	orxLOG("Physics Event");
    
    	orxOBJECT * senderObj = orxOBJECT(_pstEvent->hSender);
    	orxOBJECT * recipientObj = orxOBJECT(_pstEvent->hRecipient);
    
    	if (_pstEvent->eID == orxPHYSICS_EVENT_CONTACT_ADD) // experimental
    	{
    		orxLOG("Mango");
    
    		orxLOG("senderObj: %p", senderObj);
    		orxLOG("recipientObj: %p", recipientObj);
    		{ 
    			orxBODY * senderBody = orxBODY(senderObj);
    			orxBODY * recipientBody = orxBODY(recipientObj);
    			orxLOG("senderBody: %p", senderBody);
    			orxLOG("recipientBody: %p", recipientBody);
    
    			/*
    
    			orxBODY_PART *bodyPart;
    			for (bodyPart = orxBody_GetNextPart(senderBody, NULL); bodyPart != NULL;
    					orxBody_GetNextPart(senderBody, bodyPart))
    			{
    				orxBODY_PART_DEF * p = (orxBODY_PART_DEF *) bodyPart;
    				if (p->u32Flags && orxBODY_PART_DEF_KU32_FLAG_MESH)
    				{
    					orxLOG("isMesh");
    				}
    
    			}
    			*/
    		}
    	}
    
    	HasMass * massObject = dynamic_cast<HasMass *>((ScrollObject *) orxObject_GetUserData(senderObj));
    
    	orxLOG("Physics Event, mass object: %s", massObject ? "Yes" : "No");
    	if (massObject != NULL)
    	{
    
    		orxPHYSICS_EVENT_PAYLOAD *pstPayload = (orxPHYSICS_EVENT_PAYLOAD *) _pstEvent->pstPayload;
    
    		bool airEvent = pstPayload->zRecipientPartName == sAirSensor;
    		bool waterEvent = pstPayload->zRecipientPartName == sWaterSensor;
    		bool contact = !orxVector_IsNull(&pstPayload->vNormal);
    
    		if (airEvent && massObject->isTouchingWater())
    			massObject->setTouchingAir(contact);
    
    		if (waterEvent && massObject->isTouchingAir())
    			massObject->setTouchingWater(contact);
    
    		return orxSTATUS_SUCCESS;
    	}
    	return orxSTATUS_FAILURE;
    }
    
  • edited June 2013
    Now that I think about it, the body object is almost certainly a child of the object I'm trying to convert to an orxBODY.
  • edited June 2013
    epoulsen wrote:
    Iarwain,

    Thanks for your timely and helpful reply.
    My pleasure, happy to help if I can.
    This leads to a new question: How do I get the vertices of a body part from within an events handler.

    I see that orxBODY has an orxBODY_GetNextPart function, but when I attempt to get an orxBODY * using orxBODY(), I only ever get null.
    As you've already guessed, the orxBODY is a component of the orxOBJECT you get from the event. A call to orxOBJECT_GET_STRUCTURE(pstObject, BODY) should do the trick.

    That being said, as you're using Scroll, I'd recommend overriding the method ScrollObject::OnCollide() instead of listening to the contact event.
    Not sure how orxBODY is different/same as orxPHYSICS_BODY.
    Globally, the orxPhysics API is the low level part mostly used by orx itself and whose structures are dependent on the plugin implementation and never exposed to orx's core, and even less to orx users, whereas the orxBody API is the "public" one.

    As for getting the list of vertices, you'll have to push the part name's config section and read the vertex list from there. Don't forget they're expressed in local coordinates, so if you want them in world coordinates, you'll have to transform them by using the object's world position/rotation/scale.
    The easy way to do that is to create an orxFRAME, set your object as parent, then set its local position with the vertex info and finally get the world position out of it. There's an enum to control which coord of the local or global you want to access with the Set/Get accessors.
  • edited June 2013
    A couple of additional tricks:

    - when you have an orxSTRUCTURE and you don't know which kind it is, a call to orxStructure_GetID() will give you the answer.

    - when you have a ScrollObject and you want to downcast it to a child class of yours (and you know it's the right class), you can use the ScrollCast<MyClass>() instead of a dynamic_cast<>(). In practice it'll do a dynamic cast in debug and a static one in release, for performance sake.
  • edited June 2013
    As you've already guessed, the orxBODY is a component of the orxOBJECT you get from the event. A call to orxOBJECT_GET_STRUCTURE(pstObject, BODY) should do the trick.

    This is the code I ended up using; I get compile errors when using 'orxOBJECT_GET_STRUCTURE' and 'BODY'
    void HasMassImpl::OnCreate()
    {
        orxOBJECT * o = GetOrxObject();
        body = orxBODY(_orxObject_GetStructure(o,orxSTRUCTURE_ID_BODY));
    }
    

    That being said, as you're using Scroll, I'd recommend overriding the method ScrollObject::OnCollide() instead of listening to the contact event.

    I saw that when I was poking around; I'm going to use it instead.
  • edited June 2013
    Mmh, that's literally the definition of orxOBJECT_GET_STRUCTURE.

    What compile error do you get exactly with this code?
    void HasMassImpl::OnCreate()
    {
      orxOBJECT *o = GetOrxObject();
      body = orxOBJECT_GET_STRUCTURE(o, BODY);
    }
    
  • edited June 2013
    /*
     * HasMass.h
     *
     *  Created on: May 5, 2013
     *      Author: epoulsen
     */
    
    #ifndef HASMASS_H_
    #define HASMASS_H_
    #include "Scroll.h"
    
    class HasMass {
        public:
            virtual bool isTouchingWater() const = 0;
            virtual void setTouchingWater(bool touchingWater) = 0;
            virtual ~HasMass() { }
    
    };
    
    #endif /* HASMASS_H_ */
    
    /*
     * HasMassImple.h
     *
     *  Created on: May 6, 2013
     *      Author: epoulsen
     */
    
    #ifndef HASMASSIMPLE_H_
    #define HASMASSIMPLE_H_
    
    #include "HasMass.h"
    #include "Scroll.h" 
        
    class HasMassImpl: public HasMass, public ScrollObject {
        
        private:
            bool touchingWater;
            orxBODY * body;
        
        public:
            virtual void OnCreate();
            virtual bool isTouchingWater() const;
            virtual void setTouchingWater(bool touchingWater);
            virtual ~HasMassImpl() { } ;
        
        private:
            void updatePhysicsSettings();
    };
    
    #endif /* HASMASSIMPLE_H_ */
    
    /*
     * HasMassImpl.cpp
     *
     *  Created on: May 6, 2013
     *      Author: epoulsen
     */
    
    #include "HasMassImpl.h"
    
    void HasMassImpl::OnCreate()
    {
        orxOBJECT * o = GetOrxObject();
        body = orxBODY(orxObject_GetStructure(o,BODY));
        //body = orxBODY(_orxObject_GetStructure(o,orxSTRUCTURE_ID_BODY));
        orxBODY_PART * x = NULL;
    }
    
    bool HasMassImpl::isTouchingWater() const {
        return this->touchingWater;
    }
    
    
    void HasMassImpl::setTouchingWater(bool touchingWater) {
        this->touchingWater = touchingWater;
        updatePhysicsSettings();
    }
    
    
    void HasMassImpl::updatePhysicsSettings() {
    
        ScrollObject * scrollObj = dynamic_cast<ScrollObject *>(this);
    
        if (scrollObj)
        {
            orxOBJECT * senderObj = scrollObj->GetOrxObject();
            orxBODY * body = orxOBJECT_GET_STRUCTURE(senderObj, BODY);
            orxLOG("updatePhysicsSettings() touchingWater: %s", isTouchingWater() ? "YES" : "NO");
        }
    }
    

    Building file: ../src/HasMassImpl.cpp
    Invoking: GCC C++ Compiler
    g++ -I"/home/epoulsen/Pommelo/orx/code/include" -I"/home/epoulsen/Pommelo/scroll/include/Scroll" -O0 -g3 -Wall -c -fmessage-length=0 -m32 -MMD -MP -MF"src/HasMassImpl.d" -MT"src/HasMassImpl.d" -o "src/HasMassImpl.o" "../src/HasMassImpl.cpp"
    ../src/HasMassImpl.cpp: In member function ‘virtual void HasMassImpl::OnCreate()’:
    ../src/HasMassImpl.cpp:13:9: error: ‘BODY’ was not declared in this scope
    ../src/HasMassImpl.cpp:13:9: error: ‘orxObject_GetStructure’ was not declared in this scope
    ../src/HasMassImpl.cpp:15:20: warning: unused variable ‘x’ [-Wunused-variable]
    ../src/HasMassImpl.cpp: In member function ‘void HasMassImpl::updatePhysicsSettings()’:
    ../src/HasMassImpl.cpp:36:13: warning: unused variable ‘body’ [-Wunused-variable]
    make: *** [src/HasMassImpl.o] Error 1
    
  • edited June 2013
    Ah, I was making the mistake of thinking the #define simply omitted the leading underscore.
  • edited June 2013
    As for getting the list of vertices, you'll have to push the part name's config section and read the vertex list from there. Don't forget they're expressed in local coordinates, so if you want them in world coordinates, you'll have to transform them by using the object's world position/rotation/scale.
    The easy way to do that is to create an orxFRAME, set your object as parent, then set its local position with the vertex info and finally get the world position out of it. There's an enum to control which coord of the local or global you want to access with the Set/Get accessors.

    Doesn't this assume that it follows the BodyPartMeshTemplate, and as such has a 'VertexList' parameter?

    My particular body part looks like this:
    [bpPenguin]
    Type        = box
    TopLeft     = (0, 48, 0)
    BottomRight = (149, 92, 0)
    Restitution = 0.0
    Friction    = 1.0
    SelfFlags   = 0x0002
    CheckMask   = 0xFFFF
    Solid       = true
    Density     = 3
    

    For my (admittedly narrow) purpose, it would be nice to extract vertices from an already existing orxBODY_PART structure. Naturally spheres would return no vertices, and be a special case.

    WRT use of orxFRAME, would I do setPosition with orxFRAME_SPACE_LOCAL followed by getPosition with orxFRAME_SPACE_GLOBAL? GLOBAL == WORLD?
  • edited June 2013
    Ah, I was making the mistake of thinking the #define simply omitted the leading underscore.
    Yes, that would be orxOBJECT_GET_STRUCTURE, not orxObject_GetStructure, all the macros are capitalized.
    Doesn't this assume that it follows the BodyPartMeshTemplate, and as such has a 'VertexList' parameter?
    Yes, I assumed you had a mesh. It works the same for a box though, you can get the four vertices from the TopLeft/BottomRight values.

    Unfortunately orx doesn't have the info on the vertices either, I'd need to extract them all the way down from the plugin implementation, it feels like a big change affecting 3 different API layers (plugin, internal, public) for very little benefit. I'll think about it more.
    WRT use of orxFRAME, would I do setPosition with orxFRAME_SPACE_LOCAL followed by getPosition with orxFRAME_SPACE_GLOBAL? GLOBAL == WORLD?

    Yes and yes. :)
  • edited June 2013
    Unfortunately orx doesn't have the info on the vertices either, I'd need to extract them all the way down from the plugin implementation, it feels like a big change affecting 3 different API layers (plugin, internal, public) for very little benefit. I'll think about it more.

    Would you accept a patch?
  • edited June 2013
    Sure, the easiest way is to clone the repository in your own bitbucket account and then submit a pull request from there, it allows a better workflow, including per-line discussions.

    Out of curiosity which approach are you going to take?
  • edited June 2013
    Should I use the 'fork' button in bitbucket?
  • edited June 2013
    Out of curiosity which approach are you going to take?

    Looks like I'll need to add a new function to the physics plugin api, and work my way up from there. Is there any other way?
  • edited June 2013
    Well, the alternative is to re-extract the data from source if available, ie. the config.
    I' d support circle shapes as well, by making a regular convex polygon with the max allowed vertices for a shape, I think it's 8 by default for Box2D.

    And yes, the fork button is what you want.
  • edited June 2013
    As for getting the list of vertices, you'll have to push the part name's config section and read the vertex list from there. Don't forget they're expressed in local coordinates, so if you want them in world coordinates, you'll have to transform them by using the object's world position/rotation/scale.
    The easy way to do that is to create an orxFRAME, set your object as parent, then set its local position with the vertex info and finally get the world position out of it. There's an enum to control which coord of the local or global you want to access with the Set/Get accessors.

    This is causing me a bit of trouble; it seems that it re-applies the Scale argument ... ? It was late last night, and I couldn't investigate further. I'll check it out tonight.
  • edited June 2013
    I'm not sure what you mean by that. The list coming from config doesn't have any scale applied yet, so the local->global should only apply it once, as well as translation and rotation.

    If you get your list of vertices from Box2D itself, you should ask it to get them in world coordinates and not in Box2D local (which are different from orx's local as Box2D doesn't do scale). You'll then need to apply the ratio between Box2D units (meters) and orx's (pixels), but you should be able to retrieve that one from orx.
  • edited June 2013
    I'm not sure what you mean by that. The list coming from config doesn't have any scale applied yet, so the local->global should only apply it once, as well as translation and rotation.

    If you get your list of vertices from Box2D itself, you should ask it to get them in world coordinates and not in Box2D local (which are different from orx's local as Box2D doesn't do scale). You'll then need to apply the ratio between Box2D units (meters) and orx's (pixels), but you should be able to retrieve that one from orx.

    I have it working, and Here's what I did:

    1) Get untransformed vertices from orxPhysics plugin (from b2Fixture.shape)

    For each vertice:

    2) Multiply by the reciprocal of the dimension ratio
    3) Translate to object pivot
    4) Rotate to object rotation
    5) Translate to object position
    6) Translate to screen coordinates
    7) Draw on screen

    #1 is the new code I added to the orx code base
    #2 --> 5 are in a render handler
    #6, 7 are in a generic "Draw Polygon" function that I modified from the orxPhysics debug draw.

    I wanted to get a custom debug draw working well, as I'll use it for debugging the actual buoyancy stuff.

    Raw just-got-working code:
    orxSTATUS orxFASTCALL Game::renderEventHandler(const orxEVENT *_pstEvent) {
    
    	orxFLOAT dimensionRatio, recDimensionRatio;
    	orxPhysics_GetDimensionRatio(&dimensionRatio, &recDimensionRatio);
    	if (_pstEvent->eType == orxEVENT_TYPE_RENDER && _pstEvent->eID == orxRENDER_EVENT_OBJECT_STOP)
    	{
    		orxOBJECT * sender = (orxOBJECT *) _pstEvent->hSender;
    		orxASSERT(sender);
    		HasMassImpl * massObj = ScrollCast<HasMassImpl *>((ScrollObject *) orxObject_GetUserData(sender));
    		if (massObj != NULL && massObj->body != NULL)
    		{
    			orxVECTOR vertices[8];
    			const orxBODY_PART * part;
    
                orxVECTOR pivot, position;
                orxFLOAT rotation;
                orxObject_GetPivot(sender, &pivot);
                orxObject_GetPosition(sender, &position);
                rotation = orxObject_GetRotation(sender);
    
    			for (part = orxBody_GetNextPart(massObj->body, NULL); part != NULL; part = orxBody_GetNextPart(massObj->body, part))
    			{
    				orxS32 vertexCount = orxBody_GetPartVertices(part, &vertices[0], 8);
    				for (int i = 0; i < vertexCount; i++)
    				{
    
                        orxVECTOR &vertice = vertices[i];
    
                        orxVector_Mulf(&vertice, &vertice, recDimensionRatio);
                        orxVector_Add(&vertice, &pivot, &vertice);
                        orxVector_2DRotate(&vertice, &vertice, rotation);
                        orxVector_Add(&vertice, &vertice, &position);
    				}
    
    				orxVECTOR colVec;
    				colVec.fR = 1;
    				colVec.fG = 1;
    				colVec.fB = 1;
    				orxCOLOR col;
    				orxColor_Set(&col, &colVec, 1.0f);
    				DrawPolygon(vertices, vertexCount, col);
    
    			}
    		}
    
    	}
    
    	return orxSTATUS_SUCCESS;
    }
    
  • edited June 2013
    Thinking about it a bit more, a good solution would be to actually be able to retrieve the orxBODY_PART_DEF for any given part.
    If I were to store them that'd mean:
    - no need to modify the physics plugin interface
    - scaling bodies could be done even for programmatically defined bodies (right now we're reloading the def from config everytime the scale changed)
    - one could get all the info relevant to a body part, not just only the vertices, allowing for better debug drawing (sensor vs non-sensor, density, friction, restitution, collision flags/masks, ...)
    - it would work out of the box in orx's world coordinates, no dimension ratio, no scaling issues

    It should be a pretty straightforward modification and I could probably do it over the week-end.

    Sorry not to have thought about that earlier.
  • edited June 2013
    Would be as simple as making orxBODY_PART_DEF simply a field within orxBODY? Or would you rather it was a pointer?
  • edited June 2013
    Not much more complicated: they'll be referenced with a pointer and stored in a separate orxBANK. Just need the usual setup/cleanup code.
  • edited July 2013
    Okay, so I've implemented the buoyancy as seen in the linked article.

    Only one hitch. I have to take my calculated buoyancy force and divide it by 1,000,000. I had half expected that it would have to be divided by 10,000, since the dimension ratio is 100, so the mass in box2d would be the square of the dimension ratio.

    The 1,000,000 number works perfectly; objects that are 1/2 the density of the fluid will float 1/2 out of the fluid, etc.

    Any ideas?
  • edited July 2013
    Sorry, still no real computer access for me so I won't be of any real help for another few days/couple of weeks. :/

    It's weird indeed. I'll have a look when I get a computer back. If you could share your project with me, it'll make the investigation easier too, no hurries though. :)
  • edited July 2013
    You already have access as on Bitbucket (repo: Pommelo). No rush; works well enough for now.
Sign In or Register to comment.