6

I want to implement a collision library using the interface Collidable

type Collidable interface{
    BoundingBox() (float64,float64,float64,float64)
    FastCollisionCheck(c2 Collidable) bool
    DoesCollide(c2 Collidable) bool
    Collide(c2 Collidable)
}

It has predefined shapes like.

type Circle struct{
X,Y,Radius float64
}

The idea is that I can do

type Rock struct{
    collision.Circle
    ....
}

which then implements the interface Collidable, so I can pass it to a Spatial Hash Map (that expects a collidable). Only thing needed to do would be to override the Collide() function to my needs.

However the functions in type circle can not handle the type rock, even tough it has a circle embedded.

func (c1 *Circle) DoesCollide(i Collidable) bool{
    switch c2 := value.(type) {
    case Circle:
    //doesn't fire, as it is of type Rock (unknown in this package)
    //Needed is something like
    //if i_embeds_Circle then c2 := i_to_Circle 
    }
}

Is this possible? Is there a better way?

4
  • So this (play.golang.org/p/J5bvaEtLbM) would illustrate your issue? Commented Mar 13, 2015 at 18:27
  • Somewhat. Comment the Shape() function of rock (it uses the circles one then). My issue would be to not get to the default section of the select, but to the cricle one as well if I pass a rock to DoesShape() Commented Mar 13, 2015 at 19:15
  • So play.golang.org/p/rg_plLZMSM: the default: section is there to illustrate that Rock is not detected as a Circle Shaper even though Rock has Circle as an anonymous field. Commented Mar 13, 2015 at 19:21
  • Yes that would be the problem. How to get it to recognize the rock as a circle. (Which it embeds). Commented Mar 13, 2015 at 19:43

4 Answers 4

5

You are trying to use an object oriented design pattern with inheritance. This is not how to do this in Go. Beside, interface names end in 'able' in Java or equivalent object oriented languages. In Go the convention is to end interface names with 'er'.

To answer your question about the Rock, I would suggest that all thing that can collide into another thing implements the method CollisonShape() returning a collison.Shaper (e.g. Circle) that you will use to test collison. Here collison is the name of your package.

// This interface is defined in the collison package.
// Any object that may collide must implement that method.
type Collider interface {
    CollisonShape() Shaper
}

// This function defined in the collison package 
// test if two Collider collide into each other.
func Collide(c1, c2 Collider) bool {
    shape1, shape2 := c1.CollisonShape(), c2.CollisonShape()
    ...
}

// This is how you would define an object that can collide.
type Rock struct {
    shape *collison.Circle
    ...
}
// Implements the Collider interface.
// The return type must be the same as in the interface.
func (r *Rock) CollisonShape() collison.Shaper {
    return r.shape
}

As you see, we use a method to access the collison shape of the rock. This allow us to write

if collison.Collide(rock, spaceCraft) {...}

This answer your question on how to do get the collison Shape of Rock.

If you want to avoid the call to the CollisonShape() method in the Collide() function, you will have to pass directly the collison.Shaper.

The method Collide would be defined in the collison package as

func Collide(shape1, shape2 Shaper) bool {...}

You then would have to write

if collison.Collide(rock.shape, spacecraft.shape) {...}

This design would be slightly more efficient, but the price to pay is less readable code which is frown upon by experienced Go programmers.

If you want Circle to be an embedded struct in rock you would then have to define it the following way. Embedding the shape saves allocation time for the Circle and some work for the GC.

type Rock struct {
    shape collison.Circle
    ....
}

if collison.Collide(&rock.shape, &spacecraft.shape) {...}

If you want to use an anonymous embedded struct, you then would have to write

type Rock struct {
    Circle
    ....
}

if collison.Collide(&rock.Circle, &spacecraft.Rectangle) {...}

As you see, the code gets less and less readable, and less convenient to use. The shape is not abstracted anymore. Using anonymous embedded struct should be limited to the very few cases where it really make sense.

By using the initially suggested CollisonShape() method, you could easily change your Rock structure into this one without breaking any code.

type Rock struct {
    shape collison.Circle
    ...
}


func (r *Rock) CollisonShape() collison.Shaper {
    return &r.shape
}

This now make the shape and embedded struct. The use of a method to get the shape decouples the internal implementation of Rock from the access to the shape. You can change the internal implementation of Rock without needing to change code in other places.

This is one of the reason why Go doesn't support inheritence. It creates a very strong dependency and coupling between the base class and derived classes. Experience has shown that people often regret such coupling as the code evolves. Object composition is preferred and recommended and well supported by Go.

If efficiency is your goal, every Collider should have one position which changes and a bounding box with a width and height that doesn't change. You can save a few operations using these values for the bounding box overlap test. But this is another story.

Sign up to request clarification or add additional context in comments.

2 Comments

I'm wondering, if Circle itself implements Collider by having a method CollisionShape, wouldn't embedding an anonymous Circle make the Rock also implement Collider in an extension of your last example? This way, the OP could be solved simply by making Circle implement Collider and calling c2.CollisionShape().(type) instead of c2.(type) right away. Am I missing something? I feel the CollisionShape method is the essence of what makes your idea work.
In anonymous example -- Rock embedding Circle we can further use variable like i Collider holding Rock value to directly cast shaper, ok := i.(Shaper) effectively doing what OP wanted. Since we are casting to and interface we don't couple things. Is it a good option ?
0

Not sure how off the mark this is, but posting it in case it helps you in any way.

http://play.golang.org/p/JYuIRqwHCm

1 Comment

In your example the circle knows about the rock, in which case i could also simply cast the interface to it's correct type. The problem is, that collision is in it's own library, without further knowledge of the objects that take the some shapes later.
0

If you are calling a legacy library which is not extensible (and cannot detect Rock, only Circle), there seems to be only one solution, pass a Circle.

See this example, where I use a non-anonymous field 'c' for Rock
(That means Rock has to implement the interface, which is a simple delegation to Circle.Shape())

type Shaper interface {
    Shape()
}

type Circle struct{}

func (c *Circle) Shape() {}

type Rock struct{ c *Circle }

func (r *Rock) Shape() { r.Shape() }

func DoesShape(s Shaper) {
    fmt.Println("type:", reflect.TypeOf(s))
    switch st := s.(type) {
    case *Circle:
        fmt.Println("Shaper Circle %+v", st)
    default:
        fmt.Println("Shaper unknown")
    }
}

func main() {
    c := &Circle{}
    DoesShape(c)
    r := &Rock{c}
    DoesShape(r.c)
}

Then the output is:

type: *main.Circle
Shaper Circle %+v &{}
type: *main.Circle
Shaper Circle %+v &{}

5 Comments

Naming your interface Shaper instead of IShaper would probably be more idiomatic. Just sayin'. :)
In this case I can not override the Collide function that get's called when two objects collide, which I need to be custom for objects embedding the shape circle.
@user2089648 true: embedding (through an anonymous field) a type A within a type B doesn't make B an A. That means a different approach is required. If Rock implements Collidable, would at least your library call Rock's version of Collide()?
Type Rock has it's own version of Collide(), yes, but no version of DoesCollide() itself. However as Rock embedds Circle, it qualifies as a Collidable and Circle's version of DoesCollide() is called. However, DoesCollide then fails, because it can't cast the Argument "c2 Collidable" to a Circle.
@user2089648 ok, so the all embedded approach isn't the right one here.
0

I have solved a similar problem by slightly modifying the base class. Not sure whether this is the solution you were looking for.

import "fmt"

type IShaper interface {
    Shape()
}

type Rect struct{}

func (r *Rect) Shape() {}

type Circle struct{}

func (c *Circle) GetCircle() *Circle { return c }

func (c *Circle) Shape() {}

type Rock struct{ *Circle }

type ShapeWithCircle interface {
    GetCircle() *Circle
}

func DoesShape(s IShaper) {
    if sc, ok := s.(ShapeWithCircle); ok {
        fmt.Printf("Shaper Circle %+v\n", sc.GetCircle())
    } else {
        fmt.Println("Shaper unknown")
    }
}

func main() {
    DoesShape(&Circle{})
    DoesShape(&Rock{&Circle{}})
    DoesShape(&Rect{})
}

That is, add a trivial function GetCircle() so that anything embedding a Circle has a function to get the (possibly embedded) circle. Then anybody needing a circle can trivially write an interface (ShapeWithCircle here) allowing one to test whether GetCircle() is defined and, if so, call it to get the embedded circle.

Play with it at https://play.golang.org/p/IDkjTPrG3Z5 .

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.