This site requires JavaScript, please enable it in your browser!
Greenfoot back
Art
Art wrote ...

2016/3/17

getOneObjectAtOffset() misbehaving

Art Art

2016/3/17

#
A student was having issues with behavior in a program. For all my looking, I could not find the issue. After many hours, I have distilled the problem to the basics, but still need some help explaining why? First, a very basic world:
    public WombatWorld() 
    {
        super(10, 10, 60);        
        setBackground("cell.jpg");
        setPaintOrder(Wombat.class, BadLeaf.class);  // draw wombat on top of leaf
        addObject(new  Wombat(), 4, 7);
        addObject(new BadLeaf(), 3, 5);  
        addObject(new BadLeaf(), 2, 9);
    }
... an equally simple BadLeaf class
    public void act() 
    { 
    }    

    public void addedToWorld(World MyWorld) {
        int imageCellSize = MyWorld.getCellSize();
        GreenfootImage MyImage = getImage();
        MyImage.scale(imageCellSize, imageCellSize);
        // removal of the following line eliminates the problem!?!?!?
        Actor wombat = getOneObjectAtOffset(0, 0, Wombat.class);
    }
... and a stripped down Wombat class, looking for those bad leaves...
    public boolean facingBadleafMethod()
    {
        boolean facingBadleaf = false;
        int rotation = getRotation();
        
        int xOffset = 0;
        int yOffset = 0;
        
        switch (rotation) {
            case 0:
                xOffset = 1;
                yOffset = 0;
                break;
            case 90:
                xOffset = 0;
                yOffset = 1;
                break;
            case 180:
                xOffset = -1;
                yOffset = 0;
                break;
            case 270:
                xOffset = 0;
                yOffset = -1;
                break;
        }

        Actor Badleaf = getOneObjectAtOffset(xOffset, yOffset, BadLeaf.class);
        
        if (Badleaf != null) {
            System.out.println("I am at " + getX() + "," + getY());
            System.out.println("I am facing " + rotation);
            System.out.println("I am facing a bad leaf right" + Badleaf.toString());
            System.out.println("The bad leaf is at " + Badleaf.getX() + "," + Badleaf.getY());
            facingBadleaf = true;
        } else {
            facingBadleaf = false;
        }
       
        if (facingBadleaf == true) {
            System.out.println("I am facing a bad leaf \n");
            return(true);
        } else {
            System.out.println("I am not facing a bad leaf \n");
            return(false);
        }

    }
When the code executes, here's what happens...
[b]I am at 4,7[/b]
I am facing 0
I am facing a bad leaf rightBadLeaf@11cdf63
[b]The bad leaf is at 2,9[/b]
I am facing a bad leaf 
Huh? Maybe it's just my eyes! Seems like the getObjectAtOffset() is behaving like getObjectsInRange()? But without the line in BadLeaf's addedToWorld(), all is fine?!?!
Super_Hippo Super_Hippo

2016/3/17

#
Well, I tried it out. When I execute the 'facingBadleafMethod', it just gives me 'I am not facing a bad leaf' as it should. No matter if the line is there or not. But for me, the question is... what should this line do at all?
davmac davmac

2016/3/17

#
Art, what version of Greenfoot are you using?
danpost danpost

2016/3/17

#
I do not see how line 10 of the 'addedToWorld' method in the BadLeaf class could possibly have any influence on anything. It declares a local variable of type Actor and named 'wombat' and gets assigned 'null' (as no leaf is being placed at the location of where the wombat was placed into the world). Then the 'addedToWorld' method is done executing and transferred back to the 'addObject' line in the World subclass (WombatWorld) constructor. The variable is canned at this point as it was a local variable and has no scope beyond that specific execution of the 'addedToWorld' method.
AtoZog AtoZog

2016/3/17

#
Some follow-up... 1) To answer davmac's question, sorry for omitting so important a fact. We are running Greenfoot 3.0.2 2) To answer Super_Hippo, in this code, the line accomplishes nothing. The line was part of code that checked to make sure different objects weren't populating the same cell 3) To danpost, I agree that it shouldn't, but it does somehow. Hence my confusion. I've reproduced it on two separate machines using the same gfar file.
davmac davmac

2016/3/17

#
I'm afraid I can't reproduce the problem with the code you've posted. The wombat is not facing a bad leaf, so calling `isFacingBadLeaf` just prints "I am not facing a bad leaf". Perhaps you could upload the whole scenario.
AtoZog AtoZog

2016/3/17

#
(Sorry for the delayed update... waiting for approval before posting is the culprit.) I have uncovered the cause of the problem using the debugger to trace some of the Greenfoot inner code, specifically greenfoot.collision.ibsp.Rect.getX(), etc. I believe the issue is related to the width/height of the original image with the object. The image the student chose is 512x433, but is scaled down to fit a cell using the code shown in lines 6-8 in BadLeaf. (Seemed like a good idea at the time.) It looks like Greenfoot is using the original image size in its collision detection code, which makes the leaf appear to be in the cell being checked. If I replace the image with a smaller one, it seems to work OK. So the question is: what is the correct behavior? Regardless, I'm still not sure why the inclusion/exclusion of line 10 in the BadLeaf class makes a difference.
Super_Hippo Super_Hippo

2016/3/17

#
Wow, I can reproduce it now. Interesting thing. Cool that you found that out!
davmac davmac

2016/3/17

#
Ok; this is a known problem, actually. But I don't see why removal of the line in the BadLeaf class would make a difference, either. I'd still like to see the scenario, if possible.
AtoZog AtoZog

2016/3/17

#
Thanks for the pointer to the known bug report. Other than the image, there's not much to the scenario, but I'll gladly make it available. I'm not sure what the protocol is for sharing a scenario (publishing something buggy doesn't seem right) so I've shared it via my Google drive. It is called image-resize-collision-bug-anomaly.gfar . Let me know if there is a better/preferred way to share.
danpost danpost

2016/3/17

#
I do not know if it would work or not -- but, it may be possible to by-pass the anomaly by creating a new image out of the scaled image:
GreenfootImage image = getImage();
int size = getWorld().getCellSize();
image.scale(size, size);
setImage(new GreenfootImage(image));
By setting the image of the actor to a new image, one that is not scaled, it may just avoid the problem. If anybody tries this, please inform here as to the results.
Super_Hippo Super_Hippo

2016/3/17

#
@danpost: With this code, the result is exactly the same. Without the other line, it works. With the line, it doesn't.
danpost danpost

2016/3/18

#
Super_Hippo wrote...
@danpost: With this code, the result is exactly the same. Without the other line, it works. With the line, it doesn't.
How about if no default image is set for the class and the image is created from the filename at my line 1.
Super_Hippo Super_Hippo

2016/3/18

#
You mean if it just uses a green foot? Then it returns 'false' in both cases. But that is probably because the original size of the foot is quite small.
davmac davmac

2016/3/22

#
It surprised me that danpost's suggestion didn't help. I looked at this myself, and found another very subtle issue in how image resizing is handled (it is really an extension of the first problem). Anyway, the following code makes the problem go away:
    public void addedToWorld(World MyWorld) {
        int imageCellSize = MyWorld.getCellSize();
        GreenfootImage myImage = new GreenfootImage(getImage());
        myImage.scale(imageCellSize, imageCellSize);
        
        setImage(myImage);

        Actor wombat = getOneObjectAtOffset(0, 0, Wombat.class);
    }
The issue is that Greenfoot caches several size characteristics, including the bounding box of each actor (after rotation). When you call setImage(...), these cached values are updated, unless the new image size matches the previous image size. So with danpost's suggested code:
GreenfootImage image = getImage();
int size = getWorld().getCellSize();
image.scale(size, size);
setImage(new GreenfootImage(image));
... The bounding box is determined by the original image (line 1); it isn't updated at line 3, due to the original bug; and on line 4, setting the image to a copy of the original image but with the same size also doesn't update the bounding box. That's what interferes with the collision checking! So, a very subtle issue here. I might manage to get a fix in for the next release of Greenfoot, although the original bug will not (yet) be fixed.
You need to login to post a reply.