├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── colliders.go ├── contact.go ├── cubez.go ├── examples ├── assets │ ├── crate1_diffuse license.txt │ ├── crate1_diffuse.png │ ├── grass_0_0 license.txt │ └── grass_0_0.png ├── ballistic.go ├── build.sh ├── cubedrop.go ├── exampleapp.go └── screenshots │ ├── ballistic-150912.jpg │ └── cubedrop-150912.jpg ├── math ├── math.go ├── matrix.go ├── matrix_test.go ├── quaternion.go ├── quaternion_test.go ├── vector.go └── vector_test.go └── rigidbody.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.dll 3 | examples/ballistic 4 | examples/cubedrop 5 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Primary Maintainer: 2 | Timothy Bogdala 3 | 4 | # The author of the cyclone-physics engine from which this is based: 5 | Ian Millington 6 | 7 | # A list of all contributors to the library: 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Timothy Bogdala 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cubez v0.1.0 2 | ============ 3 | 4 | Cubez is a 3d physics library written in the [Go][golang] programming language. It is 5 | mostly a port of [cyclone-physics][cyclone] by Ian Millington and using his book 6 | "Game Physics Engine Design" as a reference. 7 | 8 | Current Features 9 | ---------------- 10 | 11 | * Full 3d rigid body real-time physics simulation suitable for games; meaning 12 | both linear velocity as well as angular velocity are calculated. 13 | * Collision detection between collider primitives. 14 | * Primitives supported: planes, spheres, cubes. 15 | * Math library defaults to 64-bit floats but can easily be tuned down to 32-bit. 16 | 17 | Examples 18 | -------- 19 | 20 | Ballistic: shoot spheres at a cube by pressing the space bar. 21 | 22 | ![ballistic][ballistic_ss] 23 | 24 | Cubedrop: hit the space bar to drop some cubes onto the ground 25 | 26 | ![cubedrop][cubedrop_ss] 27 | 28 | OS Support 29 | ---------- 30 | 31 | Cubez is known to work on the following: 32 | 33 | * Windows 7 x64 with mingw-w64 (see this [tutorial][am_mingw64] if necessary) 34 | * Linux (Ubuntu 14.04) 35 | 36 | At present, I suspect it should work on any Windows or Linux 64-bit system for which 37 | there is an acceptable Go x64 and gcc x64 compiler set available. 38 | 39 | Support for 32-bit systems is untested. 40 | 41 | Dependencies 42 | ------------ 43 | 44 | The only dependency on the core `cubez` package is the math package included in `cubez`. 45 | 46 | For the examples, you will need GLFW 3.1.x installed on your system, and you will need 47 | to install the go-gl project's [gl][gogl_gl], [glfw][gogl_glfw] and [mathgl][gogl_mgl] 48 | libraries. Your system will also need to be OpenGL 3.3 capable. 49 | 50 | The examples use a basic OpenGL *framework-in-a-file* inspired by my graphics engine 51 | called [fizzle][fizzle]. This way the full [fizzle][fizzle] library is not a dependency. 52 | 53 | Installation 54 | ------------ 55 | 56 | If you don't have the dependencies for the examples and wish to install them, 57 | you can do so with the following commands: 58 | 59 | ```bash 60 | go get github.com/go-gl/gl/v3.3-core/gl 61 | go get github.com/go-gl/mathgl/mgl32 62 | go get github.com/go-gl/glfw/v3.1/glfw 63 | ``` 64 | 65 | The library itself can be installed with the following command: 66 | 67 | ```bash 68 | go get github.com/tbogdala/cubez 69 | ``` 70 | 71 | To build the examples, run the following in a shell: 72 | 73 | ```bash 74 | cd $GOPATH/src/github.com/tbogdala/cubez/examples 75 | ./build.sh 76 | ``` 77 | 78 | Documentation 79 | ------------- 80 | 81 | Currently, you'll have to use godoc to read the API documentation and check 82 | out the examples to figure out how to use the library. 83 | 84 | 85 | Known Limitations 86 | ----------------- 87 | 88 | * slim down the public interface to the library to only export what's needed 89 | * introduce a way to set the restitution and friction for contacts 90 | 91 | 92 | Roadmap 93 | ------- 94 | 95 | * more benchmarks 96 | * unit tests for collisions 97 | * more collision primitives 98 | * include some coarse collision detection to ease the O(n^2) pain with 99 | resolving contacts in one big slice 100 | 101 | License 102 | ------- 103 | 104 | Cubez is released under the BSD license. See the [LICENSE][license-link] file for more details. 105 | 106 | 107 | Release History 108 | --------------- 109 | 110 | No tagged releases yet. 111 | 112 | 113 | 114 | [golang]: https://golang.org/ 115 | [license-link]: https://raw.githubusercontent.com/tbogdala/cubez/master/LICENSE 116 | [cyclone]: https://github.com/idmillington/cyclone-physics 117 | [fizzle]: https://github.com/tbogdala/fizzle 118 | [gogl_gl]: https://github.com/go-gl/gl 119 | [gogl_glfw]: https://github.com/go-gl/glfw 120 | [gogl_mgl]: https://github.com/go-gl/mathgl 121 | [am_mingw64]: http://animal-machine.com/blog/150723_mingw-w64_and_Go.md 122 | 123 | [ballistic_ss]: https://raw.githubusercontent.com/tbogdala/cubez/master/examples/screenshots/ballistic-150912.jpg 124 | [cubedrop_ss]: https://raw.githubusercontent.com/tbogdala/cubez/master/examples/screenshots/cubedrop-150912.jpg 125 | -------------------------------------------------------------------------------- /colliders.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package cubez 5 | 6 | import ( 7 | m "github.com/tbogdala/cubez/math" 8 | ) 9 | 10 | // Collider is an interface for collision primitive objects to make calculating collisions 11 | // amongst a heterogenous set of objects easier. 12 | // 13 | // This interface can be used in conjuection with CheckForCollisions() to check 14 | // for collision between two primitives without having to switch on types in client code. 15 | type Collider interface { 16 | Clone() Collider 17 | CalculateDerivedData() 18 | GetBody() *RigidBody 19 | GetTransform() m.Matrix3x4 20 | CheckAgainstHalfSpace(plane *CollisionPlane, existingContacts []*Contact) (bool, []*Contact) 21 | CheckAgainstSphere(sphere *CollisionSphere, existingContacts []*Contact) (bool, []*Contact) 22 | CheckAgainstCube(secondCube *CollisionCube, existingContacts []*Contact) (bool, []*Contact) 23 | } 24 | 25 | // CollisionPlane represents a plane in space for collisions but doesn't 26 | // have an associated rigid body and is considered to be infinite. 27 | // It's primarily useful for rerepresenting immovable world geometry like 28 | // a giant ground plane. 29 | type CollisionPlane struct { 30 | // Normal is the plane's normal vector 31 | Normal m.Vector3 32 | 33 | // Offset is the distance of the plane from the origin 34 | Offset m.Real 35 | } 36 | 37 | // CollisionCube is a rigid body that can be considered an axis-alligned cube 38 | // for contact collision. 39 | type CollisionCube struct { 40 | // Body is the RigidBody that is represented by this collision object. 41 | Body *RigidBody 42 | 43 | // Offset is the matrix that gives the offset of this primitive from Body. 44 | Offset m.Matrix3x4 45 | 46 | // transform is calculated by combining the Offset of the primitive with 47 | // the transform of the Body. 48 | // NOTE: this is calculated by calling CalculateDerivedData(). 49 | transform m.Matrix3x4 50 | 51 | // Halfsize holds the cube's half-sizes along each of its local axes. 52 | HalfSize m.Vector3 53 | } 54 | 55 | // CollisionSphere is a rigid body that can be considered a sphere 56 | // for collision detection. 57 | type CollisionSphere struct { 58 | // Body is the RigidBody that is represented by this collision object. 59 | Body *RigidBody 60 | 61 | // Offset is the matrix that gives the offset of this primitive from Body. 62 | Offset m.Matrix3x4 63 | 64 | // transform is calculated by combining the Offset of the primitive with 65 | // the transform of the Body. 66 | // NOTE: this is calculated by calling CalculateDerivedData(). 67 | transform m.Matrix3x4 68 | 69 | // Radius is the radius of the sphere. 70 | Radius m.Real 71 | } 72 | 73 | /* 74 | ================================================================================================== 75 | COLLISION PLANE 76 | ================================================================================================== 77 | */ 78 | 79 | // NewCollisionPlane creates a new CollisionPlane object with the 80 | // normal and offset specified. 81 | func NewCollisionPlane(n m.Vector3, o m.Real) *CollisionPlane { 82 | plane := new(CollisionPlane) 83 | plane.Normal = n 84 | plane.Offset = o 85 | return plane 86 | } 87 | 88 | // Clone makes a new copy of the CollisionPlane object 89 | func (p *CollisionPlane) Clone() Collider { 90 | newPlane := NewCollisionPlane(p.Normal, p.Offset) 91 | return newPlane 92 | } 93 | 94 | // CalculateDerivedData currently doesn't do anything for planes. 95 | func (p *CollisionPlane) CalculateDerivedData() { 96 | } 97 | 98 | // GetTransform returns an identity transform since the collision plane doesn't use transform matrixes. 99 | func (p *CollisionPlane) GetTransform() m.Matrix3x4 { 100 | var m m.Matrix3x4 101 | m.SetIdentity() 102 | return m 103 | } 104 | 105 | // GetBody returns nil since the plane doesn't have a rigid body associated with it 106 | func (p *CollisionPlane) GetBody() *RigidBody { 107 | return nil 108 | } 109 | 110 | // CheckAgainstHalfSpace doesn't return collisions against another plane, so this implementation is empty. 111 | func (p *CollisionPlane) CheckAgainstHalfSpace(plane *CollisionPlane, existingContacts []*Contact) (bool, []*Contact) { 112 | return false, existingContacts 113 | } 114 | 115 | // CheckAgainstSphere checks for collisions against a sphere. 116 | func (p *CollisionPlane) CheckAgainstSphere(sphere *CollisionSphere, existingContacts []*Contact) (bool, []*Contact) { 117 | // use the sphere's implementation of the check 118 | return sphere.CheckAgainstHalfSpace(p, existingContacts) 119 | } 120 | 121 | // CheckAgainstCube checks for collisions against a cube. 122 | func (p *CollisionPlane) CheckAgainstCube(cube *CollisionCube, existingContacts []*Contact) (bool, []*Contact) { 123 | // use the cube's implemtnation of the check 124 | return cube.CheckAgainstHalfSpace(p, existingContacts) 125 | } 126 | 127 | /* 128 | ================================================================================================== 129 | COLLISION SPHERE 130 | ================================================================================================== 131 | */ 132 | 133 | // NewCollisionSphere creates a new CollisionSphere object with the radius specified 134 | // for a given RigidBody. If a RigidBody is not specified, then a new RigidBody 135 | // object is created for the new collider object. 136 | func NewCollisionSphere(optBody *RigidBody, radius m.Real) *CollisionSphere { 137 | s := new(CollisionSphere) 138 | s.Offset.SetIdentity() 139 | s.Radius = radius 140 | s.Body = optBody 141 | if s.Body == nil { 142 | s.Body = NewRigidBody() 143 | } 144 | return s 145 | } 146 | 147 | // Clone makes a new copy of the CollisionSphere object 148 | func (s *CollisionSphere) Clone() Collider { 149 | var bClone *RigidBody 150 | if s.Body != nil { 151 | bClone = s.Body.Clone() 152 | } 153 | newSphere := NewCollisionSphere(bClone, s.Radius) 154 | newSphere.Offset = s.Offset 155 | newSphere.transform = s.transform 156 | return newSphere 157 | } 158 | 159 | // GetTransform returns a copy of the transform matrix for the collider object. 160 | func (s *CollisionSphere) GetTransform() m.Matrix3x4 { 161 | return s.transform 162 | } 163 | 164 | // GetBody returns the rigid body associated with the sphere. 165 | func (s *CollisionSphere) GetBody() *RigidBody { 166 | return s.Body 167 | } 168 | 169 | // CalculateDerivedData internal data from public data members. 170 | // 171 | // Constructs a transform matrix based on the RigidBody's transform and the 172 | // collision object's offset. 173 | func (s *CollisionSphere) CalculateDerivedData() { 174 | transform := s.Body.GetTransform() 175 | s.transform = transform.MulMatrix3x4(&s.Offset) 176 | } 177 | 178 | // CheckAgainstHalfSpace does a collision test on a collision sphere and a plane representing 179 | // a half-space (i.e. the normal of the plane points out of the half-space). 180 | func (s *CollisionSphere) CheckAgainstHalfSpace(plane *CollisionPlane, existingContacts []*Contact) (bool, []*Contact) { 181 | // work out the distance from the origin 182 | positionAxis := s.transform.GetAxis(3) 183 | distance := plane.Normal.Dot(&positionAxis) - s.Radius 184 | 185 | // check for intersection 186 | if distance <= plane.Offset == false { 187 | return false, existingContacts 188 | } 189 | 190 | c := NewContact() 191 | c.ContactPoint = plane.Normal 192 | c.ContactPoint.MulWith(distance + s.Radius*-1.0) 193 | c.ContactPoint.Add(&positionAxis) 194 | c.ContactNormal = plane.Normal 195 | c.Penetration = -distance 196 | c.Bodies[0] = s.Body 197 | c.Bodies[1] = nil 198 | 199 | // FIXME: 200 | // TODO: c.Friction and c.Restitution set here are test constants 201 | c.Friction = 0.9 202 | c.Restitution = 0.1 203 | 204 | contacts := append(existingContacts, c) 205 | 206 | return true, contacts 207 | } 208 | 209 | // CheckAgainstCube checks the sphere against collision with a cube. 210 | func (s *CollisionSphere) CheckAgainstCube(cube *CollisionCube, existingContacts []*Contact) (bool, []*Contact) { 211 | // use the cube's implementation of the check 212 | return cube.CheckAgainstSphere(s, existingContacts) 213 | } 214 | 215 | // CheckAgainstSphere checks the sphere against collision with another sphere. 216 | func (s *CollisionSphere) CheckAgainstSphere(secondSphere *CollisionSphere, existingContacts []*Contact) (bool, []*Contact) { 217 | // cache the sphere positions 218 | positionOne := s.transform.GetAxis(3) 219 | positionTwo := secondSphere.transform.GetAxis(3) 220 | 221 | // find the vector between the objects 222 | midline := positionOne 223 | midline.Sub(&positionTwo) 224 | size := midline.Magnitude() 225 | 226 | // see if it is large enough to connect 227 | if size <= 0.0 || size >= s.Radius+secondSphere.Radius { 228 | return false, existingContacts 229 | } 230 | 231 | // we have contact 232 | c := NewContact() 233 | 234 | c.ContactPoint = midline 235 | c.ContactPoint.MulWith(0.5) 236 | c.ContactPoint.Add(&positionOne) 237 | 238 | // we manually create the normal, because we have the size already calculated 239 | c.ContactNormal = midline 240 | c.ContactNormal.MulWith(1.0 / size) 241 | 242 | c.Penetration = s.Radius + secondSphere.Radius - size 243 | c.Bodies[0] = s.Body 244 | c.Bodies[1] = secondSphere.Body 245 | 246 | // FIXME: 247 | // TODO: c.Friction and c.Restitution set here are test constants 248 | c.Friction = 0.9 249 | c.Restitution = 0.1 250 | 251 | contacts := append(existingContacts, c) 252 | 253 | return true, contacts 254 | } 255 | 256 | /* 257 | ================================================================================================== 258 | COLLISION CUBE 259 | ================================================================================================== 260 | */ 261 | 262 | // NewCollisionCube creates a new CollisionCube object with the dimensions specified 263 | // for a given RigidBody. If a RigidBody is not specified, then a new RigidBody 264 | // object is created for the new collider object. 265 | func NewCollisionCube(optBody *RigidBody, halfSize m.Vector3) *CollisionCube { 266 | cube := new(CollisionCube) 267 | cube.Offset.SetIdentity() 268 | cube.HalfSize = halfSize 269 | cube.Body = optBody 270 | if cube.Body == nil { 271 | cube.Body = NewRigidBody() 272 | } 273 | return cube 274 | } 275 | 276 | // Clone makes a new copy of the CollisionCube object 277 | func (cube *CollisionCube) Clone() Collider { 278 | var bClone *RigidBody 279 | if cube.Body != nil { 280 | bClone = cube.Body.Clone() 281 | } 282 | newCube := NewCollisionCube(bClone, cube.HalfSize) 283 | newCube.Offset = cube.Offset 284 | newCube.transform = cube.transform 285 | return newCube 286 | } 287 | 288 | // GetTransform returns a copy of the transform matrix for the collider object. 289 | func (cube *CollisionCube) GetTransform() m.Matrix3x4 { 290 | return cube.transform 291 | } 292 | 293 | // GetBody returns the rigid body associated with the cube. 294 | func (cube *CollisionCube) GetBody() *RigidBody { 295 | return cube.Body 296 | } 297 | 298 | // CalculateDerivedData internal data from public data members. 299 | // 300 | // Constructs a transform matrix based on the RigidBody's transform and the 301 | // collision object's offset. 302 | func (cube *CollisionCube) CalculateDerivedData() { 303 | cube.transform = cube.Body.transform.MulMatrix3x4(&cube.Offset) 304 | } 305 | 306 | // CheckAgainstHalfSpace does a collision test on a collision box and a plane representing 307 | // a half-space (i.e. the normal of the plane points out of the half-space). 308 | func (cube *CollisionCube) CheckAgainstHalfSpace(plane *CollisionPlane, existingContacts []*Contact) (bool, []*Contact) { 309 | // check for an intersection -- if there is none, then we can return 310 | if !intersectCubeAndHalfSpace(cube, plane) { 311 | return false, existingContacts 312 | } 313 | 314 | // Now that we have an intersection, find the points of intersection. This can be 315 | // done by checking the eight vertices of the cube. If the cube is resting on a plane 316 | // or and edge it will be reported as four or two contact points. 317 | 318 | // setup an array of vertices 319 | var mults [8]m.Vector3 320 | mults[0] = m.Vector3{1.0, 1.0, 1.0} 321 | mults[1] = m.Vector3{-1.0, 1.0, 1.0} 322 | mults[2] = m.Vector3{1.0, -1.0, 1.0} 323 | mults[3] = m.Vector3{-1.0, -1.0, 1.0} 324 | mults[4] = m.Vector3{1.0, 1.0, -1.0} 325 | mults[5] = m.Vector3{-1.0, 1.0, -1.0} 326 | mults[6] = m.Vector3{1.0, -1.0, -1.0} 327 | mults[7] = m.Vector3{-1.0, -1.0, -1.0} 328 | 329 | contactDetected := false 330 | contacts := existingContacts 331 | for _, v := range mults { 332 | // calculate the position of the vertex 333 | v.ComponentProduct(&cube.HalfSize) 334 | vertexPos := cube.transform.MulVector3(&v) 335 | 336 | // calculate the distance from the plane 337 | vertexDistance := vertexPos.Dot(&plane.Normal) 338 | 339 | // compare it to the plane's distance 340 | if vertexDistance <= plane.Offset { 341 | // we have contact 342 | c := NewContact() 343 | 344 | // the contact point is halfway between the vertex and the plane -- 345 | // we multiply the direction by half the separation distance and 346 | // add the vertex location. 347 | c.ContactPoint = plane.Normal 348 | c.ContactPoint.MulWith(vertexDistance - plane.Offset) 349 | c.ContactPoint.Add(&vertexPos) 350 | c.ContactNormal = plane.Normal 351 | c.Penetration = plane.Offset - vertexDistance 352 | c.Bodies[0] = cube.Body 353 | c.Bodies[1] = nil 354 | 355 | contacts = append(contacts, c) 356 | contactDetected = true 357 | 358 | // FIXME: 359 | // TODO: c.Friction and c.Restitution set here are test constants 360 | c.Friction = 0.9 361 | c.Restitution = 0.1 362 | } 363 | } 364 | 365 | return contactDetected, contacts 366 | } 367 | 368 | // CheckAgainstSphere checks the cube against a sphere to see if there's a collision. 369 | func (cube *CollisionCube) CheckAgainstSphere(sphere *CollisionSphere, existingContacts []*Contact) (bool, []*Contact) { 370 | // transform the center of the sphere into cube coordinates 371 | position := sphere.transform.GetAxis(3) 372 | relCenter := cube.transform.TransformInverse(&position) 373 | // check to see if we can exclude contact 374 | if m.RealAbs(relCenter[0])-sphere.Radius > cube.HalfSize[0] || 375 | m.RealAbs(relCenter[1])-sphere.Radius > cube.HalfSize[1] || 376 | m.RealAbs(relCenter[2])-sphere.Radius > cube.HalfSize[2] { 377 | return false, existingContacts 378 | } 379 | 380 | var closestPoint m.Vector3 381 | 382 | // clamp the coordinates to the box 383 | for i := 0; i < 3; i++ { 384 | dist := relCenter[i] 385 | if dist > cube.HalfSize[i] { 386 | dist = cube.HalfSize[i] 387 | } else if dist < -cube.HalfSize[i] { 388 | dist = -cube.HalfSize[i] 389 | } 390 | closestPoint[i] = dist 391 | } 392 | 393 | // check to see if we're in contact 394 | distCheck := closestPoint 395 | distCheck.Sub(&relCenter) 396 | dist := distCheck.SquareMagnitude() 397 | if dist > sphere.Radius*sphere.Radius { 398 | return false, existingContacts 399 | } 400 | 401 | // transform the contact point 402 | closestPointWorld := cube.transform.MulVector3(&closestPoint) 403 | 404 | // we have contact 405 | c := NewContact() 406 | c.ContactPoint = closestPointWorld 407 | c.ContactNormal = closestPointWorld 408 | c.ContactNormal.Sub(&position) 409 | 410 | // if the sphere is small enough, or the engine doesn't process fast enough, 411 | // you can end up having a relCenter position that's the same as closestPoint -- 412 | // meaning that closestPoint didn't need to be clamped to cube bounds. 413 | // 414 | // since closestPoint is relCenter at this point, transforming it back to 415 | // world coordinates makes it equal to the sphere position which will not 416 | // be able to produce a contact normal. 417 | if m.RealEqual(c.ContactNormal.Magnitude(), 0.0) { 418 | // our hack for this is to simply use the sphere's velocity as the contact 419 | // normal, which is probably not the correct thing to do, but looks okay. 420 | c.ContactNormal = sphere.Body.Velocity 421 | } 422 | c.ContactNormal.Normalize() 423 | 424 | c.Penetration = sphere.Radius 425 | if !m.RealEqual(dist, 0.0) { 426 | c.Penetration -= m.RealSqrt(dist) 427 | } else { 428 | c.Penetration = 0.0 429 | } 430 | c.Bodies[0] = cube.Body 431 | c.Bodies[1] = sphere.Body 432 | 433 | contacts := append(existingContacts, c) 434 | 435 | // FIXME: 436 | // TODO: c.Friction and c.Restitution set here are test constants 437 | c.Friction = 0.9 438 | c.Restitution = 0.1 439 | 440 | return true, contacts 441 | } 442 | 443 | // penetrationOnAxis checks if the two boxes overlap along a given axis and 444 | // returns the amount of overlap. 445 | func penetrationOnAxis(one *CollisionCube, two *CollisionCube, axis *m.Vector3, toCenter *m.Vector3) m.Real { 446 | // project the half-size of one onto axis 447 | oneProject := transformToAxis(one, axis) 448 | twoProject := transformToAxis(two, axis) 449 | 450 | // Project this onto the axis 451 | distance := m.RealAbs(toCenter.Dot(axis)) 452 | 453 | // Return the overlap (i.e. positive indicates 454 | // overlap, negative indicates separation). 455 | return oneProject + twoProject - distance 456 | } 457 | 458 | func tryAxis(one *CollisionCube, two *CollisionCube, axis m.Vector3, toCenter *m.Vector3, 459 | index int, smallestPenetration m.Real, smallestCase int) (bool, m.Real, int) { 460 | // make sure we have a normalized axis, and don't check almost parallel axes 461 | if axis.SquareMagnitude() < m.Epsilon { 462 | return true, smallestPenetration, smallestCase 463 | } 464 | 465 | axis.Normalize() 466 | 467 | penetration := penetrationOnAxis(one, two, &axis, toCenter) 468 | if penetration < 0 { 469 | return false, smallestPenetration, smallestCase 470 | } 471 | 472 | if penetration < smallestPenetration { 473 | return true, penetration, index 474 | } 475 | 476 | return true, smallestPenetration, smallestCase 477 | } 478 | 479 | // fillPointFaceBoxBox is called when we know that a vertex from 480 | // box two is in contact with box one. 481 | func fillPointFaceBoxBox(one *CollisionCube, two *CollisionCube, toCenter *m.Vector3, 482 | best int, pen m.Real, existingContacts []*Contact) []*Contact { 483 | // We know which axis the collision is on (i.e. best), 484 | // but we need to work out which of the two faces on this axis. 485 | normal := one.transform.GetAxis(best) 486 | if normal.Dot(toCenter) > 0 { 487 | normal.MulWith(-1.0) 488 | } 489 | 490 | // Work out which vertex of box two we're colliding with. 491 | v := two.HalfSize 492 | if twoA0 := two.transform.GetAxis(0); twoA0.Dot(&normal) < 0 { 493 | v[0] = -v[0] 494 | } 495 | if twoA1 := two.transform.GetAxis(1); twoA1.Dot(&normal) < 0 { 496 | v[1] = -v[1] 497 | } 498 | if twoA2 := two.transform.GetAxis(2); twoA2.Dot(&normal) < 0 { 499 | v[2] = -v[2] 500 | } 501 | 502 | c := NewContact() 503 | c.ContactNormal = normal 504 | c.Penetration = pen 505 | c.ContactPoint = two.transform.MulVector3(&v) 506 | c.Bodies[0] = one.Body 507 | c.Bodies[1] = two.Body 508 | 509 | // FIXME: 510 | // TODO: c.Friction and c.Restitution set here are test constants 511 | c.Friction = 0.9 512 | c.Restitution = 0.1 513 | 514 | contacts := append(existingContacts, c) 515 | 516 | return contacts 517 | } 518 | 519 | func contactPoint(pOne *m.Vector3, dOne *m.Vector3, oneSize m.Real, 520 | pTwo *m.Vector3, dTwo *m.Vector3, twoSize m.Real, useOne bool) m.Vector3 { 521 | // If useOne is true, and the contact point is outside 522 | // the edge (in the case of an edge-face contact) then 523 | // we use one's midpoint, otherwise we use two's. 524 | //Vector3 toSt, cOne, cTwo; 525 | //real dpStaOne, dpStaTwo, dpOneTwo, smOne, smTwo; 526 | //real denom, mua, mub; 527 | 528 | smOne := dOne.SquareMagnitude() 529 | smTwo := dTwo.SquareMagnitude() 530 | dpOneTwo := dTwo.Dot(dOne) 531 | 532 | toSt := *pOne 533 | toSt.Sub(pTwo) 534 | dpStaOne := dOne.Dot(&toSt) 535 | dpStaTwo := dTwo.Dot(&toSt) 536 | 537 | denom := smOne*smTwo - dpOneTwo*dpOneTwo 538 | 539 | // Zero denominator indicates parrallel lines 540 | if m.RealAbs(denom) < m.Epsilon { 541 | if useOne { 542 | return *pOne 543 | } 544 | return *pTwo 545 | } 546 | 547 | mua := (dpOneTwo*dpStaTwo - smTwo*dpStaOne) / denom 548 | mub := (smOne*dpStaTwo - dpOneTwo*dpStaOne) / denom 549 | 550 | // If either of the edges has the nearest point out 551 | // of bounds, then the edges aren't crossed, we have 552 | // an edge-face contact. Our point is on the edge, which 553 | // we know from the useOne parameter. 554 | if mua > oneSize || mua < -oneSize || mub > twoSize || mub < -twoSize { 555 | if useOne { 556 | return *pOne 557 | } 558 | return *pTwo 559 | } 560 | 561 | cOne := *dOne 562 | cOne.MulWith(mua) 563 | cOne.Add(pOne) 564 | 565 | cTwo := *dTwo 566 | cTwo.MulWith(mub) 567 | cTwo.Add(pTwo) 568 | 569 | cOne.MulWith(0.5) 570 | cTwo.MulWith(0.5) 571 | cOne.Add(&cTwo) 572 | return cOne 573 | } 574 | 575 | // CheckAgainstCube checks for collisions against another cube. 576 | func (cube *CollisionCube) CheckAgainstCube(secondCube *CollisionCube, existingContacts []*Contact) (bool, []*Contact) { 577 | // find the vector between two vectors 578 | toCenter := secondCube.transform.GetAxis(3) 579 | oneAxis3 := cube.transform.GetAxis(3) 580 | toCenter.Sub(&oneAxis3) 581 | 582 | var ret bool 583 | pen := m.MaxValue 584 | var best = 0xffffff 585 | 586 | // Now we check each axis, returning if it gives a separating axis. 587 | // Keep track of the smallest penetration axis. 588 | for i := 0; i <= 2; i++ { 589 | ret, pen, best = tryAxis(cube, secondCube, cube.transform.GetAxis(i), &toCenter, i, pen, best) 590 | if ret == false { 591 | return false, existingContacts 592 | } 593 | } 594 | for i := 0; i <= 2; i++ { 595 | ret, pen, best = tryAxis(cube, secondCube, secondCube.transform.GetAxis(i), &toCenter, i+3, pen, best) 596 | if ret == false { 597 | return false, existingContacts 598 | } 599 | } 600 | 601 | // Store the best axis-major, in case we run into almost parallel edge collisions later 602 | bestSingleAxis := best 603 | 604 | for i := 0; i <= 2; i++ { 605 | a1 := cube.transform.GetAxis(i) 606 | a2 := secondCube.transform.GetAxis(0) 607 | cross := a1.Cross(&a2) 608 | ret, pen, best = tryAxis(cube, secondCube, cross, &toCenter, (i*3)+6, pen, best) 609 | if ret == false { 610 | return false, existingContacts 611 | } 612 | a1 = cube.transform.GetAxis(i) 613 | a2 = secondCube.transform.GetAxis(1) 614 | cross = a1.Cross(&a2) 615 | ret, pen, best = tryAxis(cube, secondCube, cross, &toCenter, (i*3)+7, pen, best) 616 | if ret == false { 617 | return false, existingContacts 618 | } 619 | a1 = cube.transform.GetAxis(i) 620 | a2 = secondCube.transform.GetAxis(2) 621 | cross = a1.Cross(&a2) 622 | ret, pen, best = tryAxis(cube, secondCube, cross, &toCenter, (i*3)+8, pen, best) 623 | if ret == false { 624 | return false, existingContacts 625 | } 626 | } 627 | 628 | // We now know there's a collision, and we know which of the axes gave 629 | // the smallest penetration. We now can deal with it in different ways 630 | // depending on the case. 631 | if best < 3 { 632 | // We've got a vertex of box two on a face of box one. 633 | return true, fillPointFaceBoxBox(cube, secondCube, &toCenter, best, pen, existingContacts) 634 | } else if best < 6 { 635 | // We've got a vertex of box one on a face of box two. 636 | // We use the same algorithm as above, but swap around 637 | // one and two (and therefore also the vector between their 638 | // centres). 639 | newCenter := toCenter 640 | newCenter.MulWith(-1.0) 641 | return true, fillPointFaceBoxBox(secondCube, cube, &newCenter, best-3, pen, existingContacts) 642 | } else { 643 | // We've got an edge-edge contact. Find out which axes 644 | best -= 6 645 | oneAxisIndex := best / 3 646 | twoAxisIndex := best % 3 647 | oneAxis := cube.transform.GetAxis(oneAxisIndex) 648 | twoAxis := secondCube.transform.GetAxis(twoAxisIndex) 649 | axis := oneAxis.Cross(&twoAxis) 650 | axis.Normalize() 651 | 652 | // The axis should point from box one to box two. 653 | if axis.Dot(&toCenter) > 0 { 654 | axis.MulWith(-1.0) 655 | } 656 | 657 | // We have the axes, but not the edges: each axis has 4 edges parallel 658 | // to it, we need to find which of the 4 for each object. We do 659 | // that by finding the point in the centre of the edge. We know 660 | // its component in the direction of the box's collision axis is zero 661 | // (its a mid-point) and we determine which of the extremes in each 662 | // of the other axes is closest. 663 | ptOnOneEdge := cube.HalfSize 664 | ptOnTwoEdge := secondCube.HalfSize 665 | for i := 0; i < 3; i++ { 666 | if i == oneAxisIndex { 667 | ptOnOneEdge[i] = 0 668 | } else if oneAxis := cube.transform.GetAxis(i); oneAxis.Dot(&axis) > 0 { 669 | ptOnOneEdge[i] = -ptOnOneEdge[i] 670 | } 671 | 672 | if i == twoAxisIndex { 673 | ptOnTwoEdge[i] = 0 674 | } else if twoAxis := secondCube.transform.GetAxis(i); twoAxis.Dot(&axis) < 0 { 675 | ptOnTwoEdge[i] = -ptOnTwoEdge[i] 676 | } 677 | } 678 | 679 | // Move them into world coordinates (they are already oriented 680 | // correctly, since they have been derived from the axes). 681 | ptOnOneEdge = cube.transform.MulVector3(&ptOnOneEdge) 682 | ptOnTwoEdge = secondCube.transform.MulVector3(&ptOnTwoEdge) 683 | 684 | // So we have a point and a direction for the colliding edges. 685 | // We need to find out point of closest approach of the two 686 | // line-segments. 687 | useOne := false 688 | if bestSingleAxis > 2 { 689 | useOne = true 690 | } 691 | contactVertex := contactPoint(&ptOnOneEdge, &oneAxis, cube.HalfSize[oneAxisIndex], 692 | &ptOnTwoEdge, &twoAxis, secondCube.HalfSize[twoAxisIndex], useOne) 693 | 694 | // finally ... create a new contact 695 | c := NewContact() 696 | c.ContactNormal = axis 697 | c.Penetration = pen 698 | c.ContactPoint = contactVertex 699 | c.Bodies[0] = cube.Body 700 | c.Bodies[1] = secondCube.Body 701 | 702 | // FIXME: 703 | // TODO: c.Friction and c.Restitution set here are test constants 704 | c.Friction = 0.9 705 | c.Restitution = 0.1 706 | 707 | contacts := append(existingContacts, c) 708 | return true, contacts 709 | } 710 | } 711 | 712 | /* 713 | ================================================================================================== 714 | UTILITY 715 | ================================================================================================== 716 | */ 717 | 718 | // CheckForCollisions will check one collider primitive against another and update the contact slice 719 | // if there were any contacts (as well as returning a bool indicating if contacts were found). 720 | func CheckForCollisions(one Collider, two Collider, existingContacts []*Contact) (bool, []*Contact) { 721 | switch two.(type) { 722 | case *CollisionSphere: 723 | otherSphere, ok := two.(*CollisionSphere) 724 | if ok { 725 | return one.CheckAgainstSphere(otherSphere, existingContacts) 726 | } 727 | return false, existingContacts 728 | 729 | case *CollisionCube: 730 | otherCube, ok := two.(*CollisionCube) 731 | if ok { 732 | return one.CheckAgainstCube(otherCube, existingContacts) 733 | } 734 | return false, existingContacts 735 | 736 | case *CollisionPlane: 737 | otherPlane, ok := two.(*CollisionPlane) 738 | if ok { 739 | return one.CheckAgainstHalfSpace(otherPlane, existingContacts) 740 | } 741 | return false, existingContacts 742 | } 743 | 744 | // this is reached if we dont have a supported Check* function in the interface 745 | // for the primitive type. 746 | return false, existingContacts 747 | } 748 | 749 | // intersectCubeAndHalfSpace tests to see if a cube and plane intersect 750 | func intersectCubeAndHalfSpace(cube *CollisionCube, plane *CollisionPlane) bool { 751 | // work out the projected radius of the cube onto the plane normal 752 | projectedRadius := transformToAxis(cube, &plane.Normal) 753 | 754 | // work out how far the box is from the origin 755 | axis := cube.transform.GetAxis(3) 756 | cubeDistance := plane.Normal.Dot(&axis) - projectedRadius 757 | 758 | // check for intersection 759 | return cubeDistance <= plane.Offset 760 | } 761 | 762 | func transformToAxis(cube *CollisionCube, axis *m.Vector3) m.Real { 763 | cubeAxisX := cube.transform.GetAxis(0) 764 | cubeAxisY := cube.transform.GetAxis(1) 765 | cubeAxisZ := cube.transform.GetAxis(2) 766 | 767 | return cube.HalfSize[0]*m.RealAbs(axis.Dot(&cubeAxisX)) + 768 | cube.HalfSize[1]*m.RealAbs(axis.Dot(&cubeAxisY)) + 769 | cube.HalfSize[2]*m.RealAbs(axis.Dot(&cubeAxisZ)) 770 | } 771 | -------------------------------------------------------------------------------- /contact.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package cubez 5 | 6 | import ( 7 | m "github.com/tbogdala/cubez/math" 8 | ) 9 | 10 | // a few internal epsilon values 11 | const ( 12 | velocityEpsilon m.Real = 0.01 13 | positionEpsilon m.Real = 0.01 14 | ) 15 | 16 | // Contact holds all of the data associated with a contact between two collider primitives. 17 | type Contact struct { 18 | // Bodies holds 1 or 2 bodies involved in the contact; second body can be nil 19 | Bodies [2]*RigidBody 20 | 21 | // Friction holds the lateral friction coefficient at the contact 22 | Friction m.Real 23 | 24 | // Restitution holdes the normal restitution coefficient at the contact 25 | Restitution m.Real 26 | 27 | // ContactPoint is the position of the contact in World Space 28 | ContactPoint m.Vector3 29 | 30 | // ContactNormal is the direction of the contact in World Space 31 | ContactNormal m.Vector3 32 | 33 | // Penetration is the depth of penetration at the contact point. If 34 | // both Bodies are set, then this should be midway between the 35 | // inter-penetrating points. 36 | Penetration m.Real 37 | 38 | // contactToWorld is a transform matrix that converts coordiantes in the contact's 39 | // frame of reference to World coordinates. The columns are orthornomal vectors. 40 | contactToWorld m.Matrix3 41 | 42 | // relativeContactPosition holds the World Space position of the contact point 43 | // relative to the center of each Body. 44 | relativeContactPosition [2]m.Vector3 45 | 46 | // contactVelocity holds the closing velocity at the point of contact. 47 | contactVelocity m.Vector3 48 | 49 | // desiredDeltaVelocity holds the required change in velocity for this contact to be resolved. 50 | desiredDeltaVelocity m.Real 51 | } 52 | 53 | // NewContact returns a new Contact object. 54 | func NewContact() *Contact { 55 | c := new(Contact) 56 | return c 57 | } 58 | 59 | func (c *Contact) calculateInternals(duration m.Real) { 60 | // make sure that if there's only one body that it's in the first spot 61 | if c.Bodies[0] == nil { 62 | c.ContactNormal.MulWith(-1.0) 63 | c.Bodies[0] = c.Bodies[1] 64 | c.Bodies[1] = nil 65 | } 66 | 67 | // make the set of axis at the contact point 68 | c.calculateContactBasis() 69 | 70 | // store the relative position of the contact to each body 71 | c.relativeContactPosition[0].Set(&c.ContactPoint) 72 | c.relativeContactPosition[0].Sub(&c.Bodies[0].Position) 73 | c.contactVelocity = c.calculateLocalVelocity(0, duration) 74 | 75 | if c.Bodies[1] != nil { 76 | c.relativeContactPosition[1].Set(&c.ContactPoint) 77 | c.relativeContactPosition[1].Sub(&c.Bodies[1].Position) 78 | 79 | contactVelocity1 := c.calculateLocalVelocity(1, duration) 80 | c.contactVelocity.Sub(&contactVelocity1) 81 | } 82 | 83 | // calculate the desired change in velocity for resolution 84 | c.calculateDesiredDeltaVelocity(duration) 85 | } 86 | 87 | func (c *Contact) calculateDesiredDeltaVelocity(duration m.Real) { 88 | const velocityLimit m.Real = 0.25 89 | var velocityFromAcc m.Real 90 | 91 | // calculate the acceleration induced velocity accumlated this frame 92 | var tempVelocity m.Vector3 93 | if c.Bodies[0].IsAwake { 94 | tempVelocity = c.Bodies[0].GetLastFrameAccelleration() 95 | tempVelocity.MulWith(duration) 96 | velocityFromAcc += tempVelocity.Dot(&c.ContactNormal) 97 | } 98 | if c.Bodies[1] != nil && c.Bodies[1].IsAwake { 99 | tempVelocity = c.Bodies[1].GetLastFrameAccelleration() 100 | tempVelocity.MulWith(duration) 101 | velocityFromAcc -= tempVelocity.Dot(&c.ContactNormal) 102 | } 103 | 104 | // if the velocity is very slow, limit the restitution 105 | restitution := c.Restitution 106 | if m.RealAbs(c.contactVelocity[0]) < velocityLimit { 107 | restitution = 0.0 108 | } 109 | 110 | // combine the bounce velocity with the removed acceleration velocity 111 | c.desiredDeltaVelocity = -c.contactVelocity[0] - restitution*(c.contactVelocity[0]-velocityFromAcc) 112 | } 113 | 114 | // Constructs an arbitrary orthonormal basis for the contact. It's stored 115 | // as a 3x3 matrix where each column is a vector for an axis. The x axis 116 | // is based off of the contact normal and the y and z axis will be generated 117 | // so that they are at right angles to it. 118 | func (c *Contact) calculateContactBasis() { 119 | var contactTangentY m.Vector3 120 | var contactTangentZ m.Vector3 121 | 122 | absContactNormalX := m.RealAbs(c.ContactNormal[0]) 123 | absContactNormalY := m.RealAbs(c.ContactNormal[1]) 124 | 125 | // check whether the z axis is nearer to the x or y axis 126 | if absContactNormalX > absContactNormalY { 127 | // generate a scaling factor to ensure results are normalized 128 | s := m.Real(1.0) / m.RealSqrt(c.ContactNormal[2]*c.ContactNormal[2]+c.ContactNormal[0]*c.ContactNormal[0]) 129 | 130 | // the new x axis is at right angles to the world y axis 131 | contactTangentY[0] = c.ContactNormal[2] * s 132 | contactTangentY[1] = 0 133 | contactTangentY[2] = c.ContactNormal[0] * -s 134 | 135 | // the new y axis is at right angles to the new x and z axes 136 | contactTangentZ[0] = c.ContactNormal[1] * contactTangentY[0] 137 | contactTangentZ[1] = c.ContactNormal[2]*contactTangentY[0] - c.ContactNormal[0]*contactTangentY[2] 138 | contactTangentZ[2] = -c.ContactNormal[1] * contactTangentY[0] 139 | } else { 140 | // generate a scaling factor to ensure results are normalized 141 | s := m.Real(1.0) / m.RealSqrt(c.ContactNormal[2]*c.ContactNormal[2]+c.ContactNormal[1]*c.ContactNormal[1]) 142 | 143 | // the new x axis is at right angles to the world y axis 144 | contactTangentY[0] = 0 145 | contactTangentY[1] = -c.ContactNormal[2] * s 146 | contactTangentY[2] = c.ContactNormal[1] * s 147 | 148 | // the new y axis is at right angles to the new x and z axes 149 | contactTangentZ[0] = c.ContactNormal[1]*contactTangentY[2] - c.ContactNormal[2]*contactTangentY[1] 150 | contactTangentZ[1] = -c.ContactNormal[0] * contactTangentY[2] 151 | contactTangentZ[2] = c.ContactNormal[0] * contactTangentY[1] 152 | } 153 | 154 | // now set the contactToWorld matrix based off of these three vectors 155 | c.contactToWorld.SetComponents(&c.ContactNormal, &contactTangentY, &contactTangentZ) 156 | } 157 | 158 | // calculateLocalVelocity calculates the velocity of the contact point on th given body. 159 | func (c *Contact) calculateLocalVelocity(bodyIndex int, duration m.Real) m.Vector3 { 160 | body := c.Bodies[bodyIndex] 161 | 162 | // work out the velocity of the contact point 163 | velocity := body.Rotation.Cross(&c.relativeContactPosition[bodyIndex]) 164 | velocity.Add(&body.Velocity) 165 | 166 | // turn the velocity into contact coordinates 167 | contactVelocity := c.contactToWorld.TransformTranspose(&velocity) 168 | 169 | // calculate the amount of velocity that is due to forces without reactions 170 | accVelocity := body.GetLastFrameAccelleration() 171 | accVelocity.MulWith(duration) 172 | accVelocity = c.contactToWorld.TransformTranspose(&accVelocity) 173 | 174 | // we ignore any component of acceleration in the contact normal direction 175 | accVelocity[0] = 0.0 176 | 177 | // add the planar velocity -- if there's enough friction they will 178 | // be removed during the velocity resolution 179 | contactVelocity.Add(&accVelocity) 180 | 181 | return contactVelocity 182 | } 183 | 184 | // matchAwakeState wakes up bodies that are in contact with a body that is awake. 185 | func (c *Contact) matchAwakeState() { 186 | // solo body collisions don't trigger a wake-up 187 | if c.Bodies[1] == nil { 188 | return 189 | } 190 | 191 | b0Awake := c.Bodies[0].IsAwake 192 | b1Awake := c.Bodies[1].IsAwake 193 | 194 | // wake up only the sleeping one 195 | if (b0Awake || b1Awake) && !(b0Awake && b1Awake) { 196 | if b0Awake { 197 | c.Bodies[1].SetAwake(true) 198 | } else { 199 | c.Bodies[0].SetAwake(true) 200 | } 201 | } 202 | } 203 | 204 | // ResolveContacts results a set of contacts for both penetration and velocity. 205 | // 206 | // NOTE: Contacts that cannot interact with each other should be passed to 207 | // separate calls of ResolveContacts for performance reasons. 208 | func ResolveContacts(maxIterations int, contacts []*Contact, duration m.Real) { 209 | // start off with some sanity checks 210 | if duration <= 0.0 || contacts == nil || len(contacts) == 0 { 211 | return 212 | } 213 | 214 | // prepares the contacts for processing 215 | prepareContacts(contacts, duration) 216 | 217 | // resolve the interpenetration problems with the contacts 218 | adjustPositions(maxIterations, contacts, duration) 219 | 220 | // resolve the velocity problems with the contacts 221 | adjustVelocities(maxIterations, contacts, duration) 222 | } 223 | 224 | // prepareContacts sets up contacts for processing by calculating internal data. 225 | func prepareContacts(contacts []*Contact, duration m.Real) { 226 | for _, e := range contacts { 227 | e.calculateInternals(duration) 228 | } 229 | } 230 | 231 | // adjustPositions resolves the positional issues with the given array of 232 | // constraints using the given number of iterations. 233 | func adjustPositions(maxIterations int, contacts []*Contact, duration m.Real) { 234 | // iteratively resolve interpenetrations in order of severity 235 | iterationsUsed := 0 236 | for iterationsUsed < maxIterations { 237 | // find the biggest penetration 238 | max := positionEpsilon 239 | index := len(contacts) 240 | for i, c := range contacts { 241 | if c.Penetration > max { 242 | max = c.Penetration 243 | index = i 244 | } 245 | } 246 | if index == len(contacts) { 247 | break 248 | } 249 | contact := contacts[index] 250 | 251 | // match the awake state at the contact 252 | contact.matchAwakeState() 253 | 254 | // resolve the penetration 255 | linearChange, angularChange := contact.applyPositionChange(max) 256 | 257 | // again this action may have changed the penetration of other bodies, 258 | // so we update contacts 259 | for _, c := range contacts { 260 | for b := 0; b < 2; b++ { 261 | if c.Bodies[b] != nil { 262 | // check for a match with each body in the newly resolved contact 263 | for d := 0; d < 2; d++ { 264 | if c.Bodies[b] == contact.Bodies[d] { 265 | deltaPosition := angularChange[d].Cross(&c.relativeContactPosition[b]) 266 | deltaPosition.Add(&linearChange[d]) 267 | 268 | // the sign of the change is positive if we're dealing with the second body 269 | // in a contact and negative otherwise (because we're subtracting the resolution). 270 | var sign m.Real = 1.0 271 | if b == 0 { 272 | sign = -1.0 273 | } 274 | c.Penetration += deltaPosition.Dot(&c.ContactNormal) * sign 275 | } 276 | } // d 277 | } 278 | } // b 279 | } // i,c 280 | 281 | iterationsUsed++ 282 | } 283 | } 284 | 285 | // applyPositionChange performs an inertia weighted penetration resolution of this contact alone. 286 | func (c *Contact) applyPositionChange(penetration m.Real) (linearChange, angularChange [2]m.Vector3) { 287 | const angularLimit m.Real = 0.2 288 | var angularInertia, linearInertia, angularMove, linearMove [2]m.Real 289 | var totalInertia m.Real 290 | 291 | // we need to work out the inertia of each object in the direction 292 | // of the contact normal due to angular inertia only 293 | for i := 0; i < 2; i++ { 294 | body := c.Bodies[i] 295 | if body == nil { 296 | continue 297 | } 298 | 299 | inverseInertiaTensor := body.GetInverseInertiaTensorWorld() 300 | 301 | // use the same procedure as for calculating frictionless velocity 302 | // change to work out the angular inertia 303 | angularInertiaWorld := c.relativeContactPosition[i].Cross(&c.ContactNormal) 304 | angularInertiaWorld = inverseInertiaTensor.MulVector3(&angularInertiaWorld) 305 | angularInertiaWorld = angularInertiaWorld.Cross(&c.relativeContactPosition[i]) 306 | angularInertia[i] = angularInertiaWorld.Dot(&c.ContactNormal) 307 | 308 | // the linear component is simply the inverse mass 309 | linearInertia[i] = body.GetInverseMass() 310 | 311 | // keep track of the total inertia from all component-wise 312 | totalInertia += linearInertia[i] + angularInertia[i] 313 | } 314 | 315 | // the logic is split into two loops so that totalInertia is completely calculated 316 | 317 | for i := 0; i < 2; i++ { 318 | body := c.Bodies[i] 319 | if body == nil { 320 | continue 321 | } 322 | 323 | // the linear and angular movements required are in proportion 324 | // to the two inverse inertias 325 | var sign m.Real = 1.0 326 | if i != 0 { 327 | sign = -1.0 328 | } 329 | angularMove[i] = sign * penetration * (angularInertia[i] / totalInertia) 330 | linearMove[i] = sign * penetration * (linearInertia[i] / totalInertia) 331 | 332 | // to avoid angular projections that are too great (when mass is large, but 333 | // inertia tensor is small) limit the angular move. 334 | projection := c.relativeContactPosition[i] 335 | projection.AddScaled(&c.ContactNormal, -c.relativeContactPosition[i].Dot(&c.ContactNormal)) 336 | 337 | // use the small angle approximation for the sine of the angle (i.e. the 338 | // magnitude would be sin(angularLimit) * projection.magnitude 339 | // but we approximate sin(angularLimit) to angularLimit. 340 | maxMagnitude := angularLimit * projection.Magnitude() 341 | 342 | if angularMove[i] < -maxMagnitude { 343 | totalMove := angularMove[i] + linearMove[i] 344 | angularMove[i] = -maxMagnitude 345 | linearMove[i] = totalMove - angularMove[i] 346 | } else if angularMove[i] > maxMagnitude { 347 | totalMove := angularMove[i] + linearMove[i] 348 | angularMove[i] = maxMagnitude 349 | linearMove[i] = totalMove - angularMove[i] 350 | } 351 | 352 | // we have the linear amount of movement required by turning the RigidBody 353 | // (in angularMove[i]); we now need to calculate the desired rotation to achieve that. 354 | if angularMove[i] == 0.0 { 355 | // easy -- no movement means no rotation 356 | angularChange[i].Clear() 357 | } else { 358 | // work out the direction we'd like to rotate in 359 | targetAngularDirection := c.relativeContactPosition[i].Cross(&c.ContactNormal) 360 | inverseInertiaTensor := body.GetInverseInertiaTensorWorld() 361 | angularChange[i] = inverseInertiaTensor.MulVector3(&targetAngularDirection) 362 | angularChange[i].MulWith(angularMove[i] / angularInertia[i]) 363 | } 364 | 365 | // velocity change is easier -- it's just the linear movement along ContactNormal 366 | linearChange[i] = c.ContactNormal 367 | linearChange[i].MulWith(linearMove[i]) 368 | 369 | // now we can start to apply the values we've calculated, starting with linear movement 370 | body.Position.AddScaled(&c.ContactNormal, linearMove[i]) 371 | 372 | // now we do the change in orientation 373 | body.Orientation.AddScaledVector(&angularChange[i], 1.0) 374 | body.Orientation.Normalize() 375 | 376 | // we need to calculate the derived data for body that is asleep, so that 377 | // the changes are reflected in the object's data. otherwise the resolution 378 | // will not change the position of the object and the next collision detection 379 | // round will have the same penetration. 380 | if body.IsAwake == false { 381 | body.CalculateDerivedData() 382 | } 383 | } 384 | 385 | return 386 | } 387 | 388 | // adjustVelocities resolves the velocity issues with the given array of constraints, 389 | // using the given number of iterations. 390 | func adjustVelocities(maxIterations int, contacts []*Contact, duration m.Real) { 391 | // iteratively handle impacts in order of severity 392 | iterationsUsed := 0 393 | for iterationsUsed < maxIterations { 394 | max := velocityEpsilon 395 | index := len(contacts) 396 | for i, c := range contacts { 397 | 398 | if c.desiredDeltaVelocity > max { 399 | max = c.desiredDeltaVelocity 400 | index = i 401 | } 402 | } 403 | if index == len(contacts) { 404 | break 405 | } 406 | contact := contacts[index] 407 | 408 | // match the awake state at the contact 409 | contact.matchAwakeState() 410 | 411 | // do the resolution on the contact that came out on top 412 | velocityChange, rotationChange := contact.applyVelocityChange() 413 | 414 | // with the change in velocity of the two bodies, the update of contact 415 | // velocities means that some of the relative closing velocities need recomputing. 416 | for _, c2 := range contacts { 417 | // check each body 418 | for b := 0; b < 2; b++ { 419 | if c2.Bodies[b] == nil { 420 | continue 421 | } 422 | // check for a match with each body in the newly resolved contact 423 | for d := 0; d < 2; d++ { 424 | if c2.Bodies[b] == contact.Bodies[d] { 425 | deltaVel := rotationChange[d].Cross(&c2.relativeContactPosition[b]) 426 | deltaVel.Add(&velocityChange[d]) 427 | 428 | // the sign of the change is negative if we're dealing with 429 | // the second body in a contact. 430 | var sign m.Real = 1.0 431 | if b == 1 { 432 | sign = -1.0 433 | } 434 | 435 | tmpContactVelocity := c2.contactToWorld.TransformTranspose(&deltaVel) 436 | tmpContactVelocity.MulWith(sign) 437 | c2.contactVelocity.Add(&tmpContactVelocity) 438 | c2.calculateDesiredDeltaVelocity(duration) 439 | } 440 | } // d 441 | } // b 442 | } // c2 443 | iterationsUsed++ 444 | } 445 | } 446 | 447 | // applyVelocityChange performs an inertia-weighted impulse based resolution of this contact alone 448 | func (c *Contact) applyVelocityChange() (velocityChange, rotationChange [2]m.Vector3) { 449 | // get hold of the inverse mass and inverse inertia tensor, both in World Space 450 | var inverseInertiaTensors [2]m.Matrix3 451 | inverseInertiaTensors[0] = c.Bodies[0].GetInverseInertiaTensorWorld() 452 | if c.Bodies[1] != nil { 453 | inverseInertiaTensors[1] = c.Bodies[1].GetInverseInertiaTensorWorld() 454 | } 455 | 456 | // we will calculate the impulse for each contact axis 457 | var impulseContact m.Vector3 458 | 459 | if c.Friction == 0.0 { 460 | // use the short format for frictionless contacts 461 | impulseContact = c.calculateFrictionlessImpulse(inverseInertiaTensors) 462 | } else { 463 | // otherwise we may have impulses that aren't in the direction of the 464 | // contact, so we need the more complex version 465 | impulseContact = c.calculateFrictionImpulse(inverseInertiaTensors) 466 | } 467 | 468 | // convert impulse to world coordinates 469 | impulse := c.contactToWorld.MulVector3(&impulseContact) 470 | 471 | // split in the impulse into linear and rotation component-wise 472 | impulsiveTorque := c.relativeContactPosition[0].Cross(&impulse) 473 | rotationChange[0] = inverseInertiaTensors[0].MulVector3(&impulsiveTorque) 474 | velocityChange[0].Clear() 475 | velocityChange[0].AddScaled(&impulse, c.Bodies[0].GetInverseMass()) 476 | 477 | // apply the changes 478 | c.Bodies[0].AddVelocity(&velocityChange[0]) 479 | c.Bodies[0].AddRotation(&rotationChange[0]) 480 | 481 | if c.Bodies[1] != nil { 482 | // work out the second body's linear and angular changes 483 | impulsiveTorque = impulse.Cross(&c.relativeContactPosition[1]) 484 | rotationChange[1] = inverseInertiaTensors[1].MulVector3(&impulsiveTorque) 485 | velocityChange[1].Clear() 486 | velocityChange[1].AddScaled(&impulse, -c.Bodies[1].GetInverseMass()) 487 | 488 | // apply the changes 489 | c.Bodies[1].AddVelocity(&velocityChange[1]) 490 | c.Bodies[1].AddRotation(&rotationChange[1]) 491 | } 492 | 493 | return 494 | } 495 | 496 | // calculateFrictionlessImpulse calculates the impulse needed to resolve this contact, 497 | // given that the contact has no friction. 498 | func (c *Contact) calculateFrictionlessImpulse(inverseInertiaTensors [2]m.Matrix3) (impulseContact m.Vector3) { 499 | // build a vector that shows the change in velocity in World Space for 500 | // a unit impulse in the direction of the contact normal 501 | deltaVelWorld := c.relativeContactPosition[0].Cross(&c.ContactNormal) 502 | deltaVelWorld = inverseInertiaTensors[0].MulVector3(&deltaVelWorld) 503 | deltaVelWorld = deltaVelWorld.Cross(&c.relativeContactPosition[0]) 504 | 505 | // work out the change in velocity in contact coordinates 506 | deltaVelocity := deltaVelWorld.Dot(&c.ContactNormal) 507 | 508 | // add the linear component of velocity change 509 | deltaVelocity += c.Bodies[0].GetInverseMass() 510 | 511 | // check if we need to process the second body's data 512 | if c.Bodies[1] == nil { 513 | // go through the same transformation sequence again 514 | deltaVelWorld = c.relativeContactPosition[1].Cross(&c.ContactNormal) 515 | deltaVelWorld = inverseInertiaTensors[1].MulVector3(&deltaVelWorld) 516 | deltaVelWorld = deltaVelWorld.Cross(&c.relativeContactPosition[1]) 517 | 518 | // work out the change in velocity in contact coordinates 519 | // NOTE: should this be a +=? 520 | deltaVelocity += deltaVelWorld.Dot(&c.ContactNormal) 521 | 522 | // add the linear component of velocity change 523 | deltaVelocity += c.Bodies[1].GetInverseMass() 524 | } 525 | 526 | // calculate the required size of the impulse 527 | impulseContact[0] = c.desiredDeltaVelocity / deltaVelocity 528 | impulseContact[1] = 0 529 | impulseContact[2] = 0 530 | return 531 | } 532 | 533 | // calculateFrictionImpulse calculates the impulse needed to resolve this contact, 534 | // given that the contact has a non-zero coefficient of friction. 535 | func (c *Contact) calculateFrictionImpulse(inverseInertiaTensors [2]m.Matrix3) (impulseContact m.Vector3) { 536 | inverseMass := c.Bodies[0].GetInverseMass() 537 | 538 | // the equivalent of a cross product in matrices is multiplication 539 | // by a skew symmetric matrix - we build the matrix for converting 540 | // between linear and angular quantities. 541 | var impulseToTorque m.Matrix3 542 | setSkewSymmetric(&impulseToTorque, &c.relativeContactPosition[0]) 543 | 544 | // build the matrix to convert contact impulse to change in velocity in 545 | // world coordinates 546 | deltaVelWorld := impulseToTorque.MulMatrix3(&inverseInertiaTensors[0]) 547 | deltaVelWorld = deltaVelWorld.MulMatrix3(&impulseToTorque) 548 | deltaVelWorld.MulWith(-1.0) 549 | 550 | // check to see if we need to add the second body's data 551 | if c.Bodies[1] != nil { 552 | // set the cross product matrix 553 | setSkewSymmetric(&impulseToTorque, &c.relativeContactPosition[1]) 554 | 555 | // calculate the velocity change matrix 556 | deltaVelWorld2 := impulseToTorque.MulMatrix3(&inverseInertiaTensors[1]) 557 | deltaVelWorld2 = deltaVelWorld2.MulMatrix3(&impulseToTorque) 558 | deltaVelWorld2.MulWith(-1.0) 559 | 560 | // add to the total delta velocity 561 | deltaVelWorld.Add(&deltaVelWorld2) 562 | 563 | // add to the inverse mass 564 | inverseMass += c.Bodies[1].GetInverseMass() 565 | } 566 | 567 | // do a change of basis to convert into contact coordinates 568 | deltaVelocity := c.contactToWorld.Transpose() 569 | deltaVelocity = deltaVelocity.MulMatrix3(&deltaVelWorld) 570 | deltaVelocity = deltaVelocity.MulMatrix3(&c.contactToWorld) 571 | 572 | // add in the linear velocity change 573 | deltaVelocity[0] += inverseMass 574 | deltaVelocity[4] += inverseMass 575 | deltaVelocity[8] += inverseMass 576 | 577 | // invert to get the impulse needed per unit velocity 578 | impulseMatrix := deltaVelocity.Invert() 579 | 580 | // find the target velocities to kill 581 | velKill := m.Vector3{ 582 | c.desiredDeltaVelocity, 583 | -c.contactVelocity[1], 584 | -c.contactVelocity[2], 585 | } 586 | 587 | // find the impulse to kill target velocities 588 | impulseContact = impulseMatrix.MulVector3(&velKill) 589 | 590 | // check for exceeding friction 591 | planarImpulse := m.RealSqrt(impulseContact[1]*impulseContact[1] + impulseContact[2]*impulseContact[2]) 592 | if planarImpulse > impulseContact[0]*c.Friction { 593 | // we need to use dynamic friction 594 | impulseContact[1] /= planarImpulse 595 | impulseContact[2] /= planarImpulse 596 | 597 | impulseContact[0] = deltaVelocity[0] + 598 | deltaVelocity[3]*c.Friction*impulseContact[1] + 599 | deltaVelocity[6]*c.Friction*impulseContact[2] 600 | impulseContact[0] = c.desiredDeltaVelocity / impulseContact[0] 601 | impulseContact[1] *= c.Friction * impulseContact[0] 602 | impulseContact[2] *= c.Friction * impulseContact[0] 603 | } 604 | 605 | return 606 | } 607 | 608 | // setSkewSymmetric sets the matrix to be a skew symmetric matrix based on 609 | // the given vector. The skew symmetric matrix is the equivalent of a vector 610 | // cross prodcut. So if a,b are vectors. a x b = A_s b 611 | // where A_s is the skew symmetrix form of a. 612 | func setSkewSymmetric(m *m.Matrix3, v *m.Vector3) { 613 | m[0], m[3], m[6] = 0.0, -v[2], v[1] 614 | m[1], m[4], m[7] = v[2], 0.0, -v[0] 615 | m[2], m[5], m[8] = -v[1], v[0], 0.0 616 | } 617 | -------------------------------------------------------------------------------- /cubez.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package cubez 5 | 6 | /* 7 | 8 | Cubez is a game physics engine written in Go that aims to be quick enough for 9 | real-time simulation at the cost of physical accuracy. 10 | 11 | */ 12 | -------------------------------------------------------------------------------- /examples/assets/crate1_diffuse license.txt: -------------------------------------------------------------------------------- 1 | crate1_diffuse.png was downloaded from http://opengameart.org/content/3-crate-textures-w-bump-normal on 9/12/2015. 2 | Author: Luke.RUSTLTD 3 | Description: 3 crate textures with bumpmaps and normal maps 4 | www.rustltd.com 5 | 6 | CC0 1.0 Universal (CC0 1.0) 7 | Public Domain Dedication 8 | 9 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. 10 | Statement of Purpose 11 | 12 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 13 | 14 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 15 | 16 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 17 | 18 | 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 19 | 20 | the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 21 | moral rights retained by the original author(s) and/or performer(s); 22 | publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 23 | rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 24 | rights protecting the extraction, dissemination, use and reuse of data in a Work; 25 | database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 26 | other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 27 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 28 | 29 | 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 30 | 31 | 4. Limitations and Disclaimers. 32 | 33 | No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 34 | Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 35 | Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 36 | Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. -------------------------------------------------------------------------------- /examples/assets/crate1_diffuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbogdala/cubez/e41617e9e489bbe27bf1e9e35e0225e4bf36d609/examples/assets/crate1_diffuse.png -------------------------------------------------------------------------------- /examples/assets/grass_0_0 license.txt: -------------------------------------------------------------------------------- 1 | Grass_0_0.png was downloaded from http://opengameart.org/content/grass-001 on 9/12/2015. 2 | Author: Lamroot 3 | Description: 512x512 seamless grass texture, made using CC-BY 3.0 photos from the Yo Frankie! DVD. 4 | (c) copyright Blender Foundation | apricot.blender.org 5 | 6 | 7 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. 8 | License 9 | 10 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 11 | 12 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 13 | 14 | 1. Definitions 15 | 16 | "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. 17 | "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. 18 | "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. 19 | "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. 20 | "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. 21 | "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. 22 | "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 23 | "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. 24 | "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 25 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 26 | 27 | 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 28 | 29 | to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; 30 | to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; 31 | to Distribute and Publicly Perform the Work including as incorporated in Collections; and, 32 | to Distribute and Publicly Perform Adaptations. 33 | For the avoidance of doubt: 34 | 35 | Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; 36 | Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, 37 | Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. 38 | The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 39 | 40 | 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 41 | 42 | You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. 43 | If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. 44 | Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 45 | 5. Representations, Warranties and Disclaimer 46 | 47 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 48 | 49 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 50 | 51 | 7. Termination 52 | 53 | This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 54 | Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 55 | 8. Miscellaneous 56 | 57 | Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 58 | Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. 59 | If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 60 | No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 61 | This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. 62 | The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. 63 | Creative Commons Notice 64 | 65 | Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. 66 | 67 | Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. 68 | 69 | Creative Commons may be contacted at https://creativecommons.org/. -------------------------------------------------------------------------------- /examples/assets/grass_0_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbogdala/cubez/e41617e9e489bbe27bf1e9e35e0225e4bf36d609/examples/assets/grass_0_0.png -------------------------------------------------------------------------------- /examples/ballistic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package main 5 | 6 | import ( 7 | gl "github.com/go-gl/gl/v3.3-core/gl" 8 | glfw "github.com/go-gl/glfw/v3.1/glfw" 9 | mgl "github.com/go-gl/mathgl/mgl32" 10 | "github.com/tbogdala/cubez" 11 | m "github.com/tbogdala/cubez/math" 12 | ) 13 | 14 | var ( 15 | app *ExampleApp 16 | 17 | cube *Entity 18 | backboard *Entity 19 | bullets []*Entity 20 | 21 | colorShader uint32 22 | groundPlane *cubez.CollisionPlane 23 | ground *Renderable 24 | ) 25 | 26 | // update object locations 27 | func updateObjects(delta float64) { 28 | // for now there's only one box to update 29 | body := cube.Collider.GetBody() 30 | body.Integrate(m.Real(delta)) 31 | cube.Collider.CalculateDerivedData() 32 | 33 | // for now we hack in the position and rotation of the collider into the renderable 34 | SetGlVector3(&cube.Node.Location, &body.Position) 35 | SetGlQuat(&cube.Node.LocalRotation, &body.Orientation) 36 | 37 | for _, bullet := range bullets { 38 | bulletBody := bullet.Collider.GetBody() 39 | bulletBody.Integrate(m.Real(delta)) 40 | bullet.Collider.CalculateDerivedData() 41 | SetGlVector3(&bullet.Node.Location, &bulletBody.Position) 42 | SetGlQuat(&bullet.Node.LocalRotation, &bulletBody.Orientation) 43 | } 44 | } 45 | 46 | // see if any of the rigid bodys contact 47 | func generateContacts(delta float64) (bool, []*cubez.Contact) { 48 | var returnFound bool 49 | 50 | // create the ground plane 51 | groundPlane := cubez.NewCollisionPlane(m.Vector3{0.0, 1.0, 0.0}, 0.0) 52 | 53 | // see if we have a collision with the ground 54 | found, contacts := cubez.CheckForCollisions(cube.Collider, groundPlane, nil) 55 | if found { 56 | returnFound = true 57 | } 58 | // see if there's a collision against the backboard 59 | found, contacts = cubez.CheckForCollisions(cube.Collider, backboard.Collider, contacts) 60 | if found { 61 | returnFound = true 62 | } 63 | 64 | // run collision checks on bullets 65 | for _, bullet := range bullets { 66 | // check against the ground 67 | found, contacts = cubez.CheckForCollisions(bullet.Collider, groundPlane, contacts) 68 | if found { 69 | returnFound = true 70 | } 71 | 72 | // check against the cube 73 | found, contacts = cubez.CheckForCollisions(cube.Collider, bullet.Collider, contacts) 74 | if found { 75 | returnFound = true 76 | } 77 | 78 | // check against the backboard 79 | found, contacts = cubez.CheckForCollisions(backboard.Collider, bullet.Collider, contacts) 80 | if found { 81 | returnFound = true 82 | } 83 | 84 | // check against other bullets 85 | for _, bullet2 := range bullets { 86 | if bullet2 == bullet { 87 | continue 88 | } 89 | found, contacts = cubez.CheckForCollisions(bullet2.Collider, bullet.Collider, contacts) 90 | if found { 91 | returnFound = true 92 | } 93 | } 94 | } 95 | 96 | return returnFound, contacts 97 | } 98 | 99 | func updateCallback(delta float64) { 100 | updateObjects(delta) 101 | foundContacts, contacts := generateContacts(delta) 102 | if foundContacts { 103 | cubez.ResolveContacts(len(contacts)*8, contacts, m.Real(delta)) 104 | } 105 | } 106 | 107 | func renderCallback(delta float64) { 108 | gl.Viewport(0, 0, int32(app.Width), int32(app.Height)) 109 | gl.ClearColor(0.196078, 0.6, 0.8, 1.0) // some pov-ray sky blue 110 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 111 | 112 | // make the projection and view matrixes 113 | projection := mgl.Perspective(mgl.DegToRad(60.0), float32(app.Width)/float32(app.Height), 1.0, 200.0) 114 | view := app.CameraRotation.Mat4() 115 | view = view.Mul4(mgl.Translate3D(-app.CameraPos[0], -app.CameraPos[1], -app.CameraPos[2])) 116 | 117 | // draw the cube 118 | cube.Node.Draw(projection, view) 119 | 120 | // draw all of the bullets 121 | for _, bullet := range bullets { 122 | bullet.Node.Draw(projection, view) 123 | } 124 | 125 | // draw the backboard 126 | backboard.Node.Draw(projection, view) 127 | 128 | // draw the ground 129 | ground.Draw(projection, view) 130 | 131 | //time.Sleep(10 * time.Millisecond) 132 | } 133 | 134 | func main() { 135 | app = NewApp() 136 | app.InitGraphics("Ballistic", 800, 600) 137 | app.SetKeyCallback(keyCallback) 138 | app.OnRender = renderCallback 139 | app.OnUpdate = updateCallback 140 | defer app.Terminate() 141 | 142 | // compile the shaders 143 | var err error 144 | colorShader, err = LoadShaderProgram(DiffuseColorVertShader, DiffuseColorFragShader) 145 | if err != nil { 146 | panic("Failed to compile the shader! " + err.Error()) 147 | } 148 | 149 | // create the ground plane 150 | groundPlane = cubez.NewCollisionPlane(m.Vector3{0.0, 1.0, 0.0}, 0.0) 151 | 152 | // make a ground plane to draw 153 | ground = CreatePlaneXZ(-500.0, 500.0, 500.0, -500.0, 1.0) 154 | ground.Shader = colorShader 155 | ground.Color = mgl.Vec4{0.6, 0.6, 0.6, 1.0} 156 | 157 | // create a test cube to render 158 | cubeNode := CreateCube(-1.0, -1.0, -1.0, 1.0, 1.0, 1.0) 159 | cubeNode.Shader = colorShader 160 | cubeNode.Color = mgl.Vec4{1.0, 0.0, 0.0, 1.0} 161 | 162 | // create the collision box for the the cube 163 | var cubeMass m.Real = 8.0 164 | var cubeInertia m.Matrix3 165 | cubeCollider := cubez.NewCollisionCube(nil, m.Vector3{1.0, 1.0, 1.0}) 166 | cubeCollider.Body.Position = m.Vector3{0.0, 5.0, 0.0} 167 | cubeCollider.Body.SetMass(cubeMass) 168 | cubeInertia.SetBlockInertiaTensor(&cubeCollider.HalfSize, cubeMass) 169 | cubeCollider.Body.SetInertiaTensor(&cubeInertia) 170 | cubeCollider.Body.CalculateDerivedData() 171 | cubeCollider.CalculateDerivedData() 172 | 173 | // make the entity out of the renerable and collider 174 | cube = NewEntity(cubeNode, cubeCollider) 175 | 176 | // make a slice of entities for bullets 177 | bullets = make([]*Entity, 0, 16) 178 | 179 | // make the backboard to bound the bullets off of 180 | backboardNode := CreateCube(-0.5, -2.0, -0.25, 0.5, 2.0, 0.25) 181 | backboardNode.Shader = colorShader 182 | backboardNode.Color = mgl.Vec4{0.25, 0.2, 0.2, 1.0} 183 | backboardCollider := cubez.NewCollisionCube(nil, m.Vector3{0.5, 2.0, 0.25}) 184 | backboardCollider.Body.Position = m.Vector3{0.0, 2.0, -10.0} 185 | backboardCollider.Body.SetInfiniteMass() 186 | backboardCollider.Body.CalculateDerivedData() 187 | backboardCollider.CalculateDerivedData() 188 | SetGlVector3(&backboardNode.Location, &backboardCollider.Body.Position) 189 | 190 | // make the backboard entity 191 | backboard = NewEntity(backboardNode, backboardCollider) 192 | 193 | // setup the camera 194 | app.CameraPos = mgl.Vec3{-3.0, 3.0, 15.0} 195 | app.CameraRotation = mgl.QuatLookAtV( 196 | mgl.Vec3{-3.0, 3.0, 15.0}, 197 | mgl.Vec3{0.0, 1.0, 0.0}, 198 | mgl.Vec3{0.0, 1.0, 0.0}) 199 | 200 | gl.Enable(gl.DEPTH_TEST) 201 | app.RenderLoop() 202 | } 203 | 204 | func fire() { 205 | var mass m.Real = 1.5 206 | var radius m.Real = 0.2 207 | 208 | // create a test sphere to render 209 | bullet := CreateSphere(float32(radius), 16, 16) 210 | bullet.Shader = colorShader 211 | bullet.Color = mgl.Vec4{0.2, 0.2, 1.0, 1.0} 212 | 213 | // create the collision box for the the bullet 214 | bulletCollider := cubez.NewCollisionSphere(nil, radius) 215 | bulletCollider.Body.Position = m.Vector3{0.0, 1.5, 20.0} 216 | 217 | var cubeInertia m.Matrix3 218 | var coeff m.Real = 0.4 * mass * radius * radius 219 | cubeInertia.SetInertiaTensorCoeffs(coeff, coeff, coeff, 0.0, 0.0, 0.0) 220 | bulletCollider.GetBody().SetInertiaTensor(&cubeInertia) 221 | 222 | bulletCollider.Body.SetMass(mass) 223 | bulletCollider.Body.Velocity = m.Vector3{0.0, 0.0, -40.0} 224 | bulletCollider.Body.Acceleration = m.Vector3{0.0, -2.5, 0.0} 225 | 226 | bulletCollider.Body.CalculateDerivedData() 227 | bulletCollider.CalculateDerivedData() 228 | 229 | e := NewEntity(bullet, bulletCollider) 230 | bullets = append(bullets, e) 231 | } 232 | 233 | func keyCallback(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 234 | // Key W == close app 235 | if key == glfw.KeyEscape && action == glfw.Press { 236 | w.SetShouldClose(true) 237 | } 238 | if key == glfw.KeySpace && action == glfw.Press { 239 | fire() 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /examples/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | go build -o ballistic ballistic.go exampleapp.go 4 | go build -o cubedrop cubedrop.go exampleapp.go 5 | -------------------------------------------------------------------------------- /examples/cubedrop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package main 5 | 6 | import ( 7 | gl "github.com/go-gl/gl/v3.3-core/gl" 8 | glfw "github.com/go-gl/glfw/v3.1/glfw" 9 | mgl "github.com/go-gl/mathgl/mgl32" 10 | "github.com/tbogdala/cubez" 11 | m "github.com/tbogdala/cubez/math" 12 | ) 13 | 14 | const ( 15 | grassTexturePath = "assets/grass_0_0.png" 16 | crateTexturePath = "assets/crate1_diffuse.png" 17 | ) 18 | 19 | var ( 20 | diffuseShader uint32 21 | app *ExampleApp 22 | cubes []*Entity 23 | groundPlane *cubez.CollisionPlane 24 | ground *Renderable 25 | crateTexture uint32 26 | ) 27 | 28 | // update object locations 29 | func updateObjects(delta float64) { 30 | for _, cube := range cubes { 31 | body := cube.Collider.GetBody() 32 | body.Integrate(m.Real(delta)) 33 | cube.Collider.CalculateDerivedData() 34 | 35 | // for now we hack in the position and rotation of the collider into the renderable 36 | SetGlVector3(&cube.Node.Location, &body.Position) 37 | SetGlQuat(&cube.Node.LocalRotation, &body.Orientation) 38 | } 39 | } 40 | 41 | // see if any of the rigid bodys contact 42 | func generateContacts(delta float64) (bool, []*cubez.Contact) { 43 | var returnFound bool 44 | var found bool 45 | var contacts []*cubez.Contact 46 | 47 | for _, cube := range cubes { 48 | // see if we have a collision with the ground 49 | found, contacts = cube.Collider.CheckAgainstHalfSpace(groundPlane, contacts) 50 | if found == true { 51 | returnFound = true 52 | } 53 | 54 | // check it against the other cubes. yes this is O(n^2) and not good practice 55 | for _, otherCube := range cubes { 56 | if cube == otherCube { 57 | continue 58 | } 59 | found, contacts = cubez.CheckForCollisions(cube.Collider, otherCube.Collider, contacts) 60 | if found == true { 61 | returnFound = true 62 | } 63 | } 64 | } 65 | 66 | return returnFound, contacts 67 | } 68 | 69 | func updateCallback(delta float64) { 70 | updateObjects(delta) 71 | foundContacts, contacts := generateContacts(delta) 72 | if foundContacts { 73 | cubez.ResolveContacts(len(contacts)*8, contacts, m.Real(delta)) 74 | } 75 | } 76 | 77 | func renderCallback(delta float64) { 78 | gl.Viewport(0, 0, int32(app.Width), int32(app.Height)) 79 | gl.ClearColor(0.196078, 0.6, 0.8, 1.0) // some pov-ray sky blue 80 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 81 | 82 | // make the projection and view matrixes 83 | projection := mgl.Perspective(mgl.DegToRad(60.0), float32(app.Width)/float32(app.Height), 1.0, 200.0) 84 | view := app.CameraRotation.Mat4() 85 | view = view.Mul4(mgl.Translate3D(-app.CameraPos[0], -app.CameraPos[1], -app.CameraPos[2])) 86 | 87 | // draw all of the cubes 88 | for _, cube := range cubes { 89 | cube.Node.Draw(projection, view) 90 | } 91 | 92 | // draw the ground 93 | ground.Draw(projection, view) 94 | } 95 | 96 | func main() { 97 | app = NewApp() 98 | app.InitGraphics("Cube Drop", 800, 600) 99 | app.SetKeyCallback(keyCallback) 100 | app.OnRender = renderCallback 101 | app.OnUpdate = updateCallback 102 | defer app.Terminate() 103 | 104 | // compile the shaders 105 | var err error 106 | diffuseShader, err = LoadShaderProgram(DiffuseTextureVertShader, DiffuseTextureFragShader) 107 | if err != nil { 108 | panic("Failed to compile the diffuse shader! " + err.Error()) 109 | } 110 | 111 | // setup the slice of cubes to render 112 | cubes = make([]*Entity, 0, 128) 113 | 114 | // load the grass texture for the ground 115 | grassTex, err := LoadImageToTexture(grassTexturePath) 116 | if err != nil { 117 | panic("Failed to load the grass texture! " + err.Error()) 118 | } 119 | 120 | // load the crate texture for the cubes 121 | crateTexture, err = LoadImageToTexture(crateTexturePath) 122 | if err != nil { 123 | panic("Failed to load the crate texture! " + err.Error()) 124 | } 125 | 126 | // create the ground plane 127 | groundPlane = cubez.NewCollisionPlane(m.Vector3{0.0, 1.0, 0.0}, 0.0) 128 | 129 | // make a ground plane to draw 130 | ground = CreatePlaneXZ(-500.0, 500.0, 500.0, -500.0, 64.0) 131 | ground.Shader = diffuseShader 132 | ground.Color = mgl.Vec4{1.0, 1.0, 1.0, 1.0} 133 | ground.Tex0 = grassTex 134 | 135 | // setup the camera 136 | app.CameraPos = mgl.Vec3{0.0, 3.0, 10.0} 137 | app.CameraRotation = mgl.QuatLookAtV( 138 | mgl.Vec3{0.0, 5.0, 10.0}, 139 | mgl.Vec3{0.0, 0.0, 0.0}, 140 | mgl.Vec3{0.0, 1.0, 0.0}) 141 | 142 | gl.Enable(gl.DEPTH_TEST) 143 | app.RenderLoop() 144 | } 145 | 146 | func fire() { 147 | const cubesToMake = 4 148 | var offset float32 = 0.0 149 | if len(cubes) > 0 && (len(cubes)/cubesToMake)%2 >= 1 { 150 | offset = 0.75 151 | } 152 | 153 | for i := 0; i < cubesToMake; i++ { 154 | e := new(Entity) 155 | e.Node = CreateCube(-0.5, -0.5, -0.5, 0.5, 0.5, 0.5) 156 | e.Node.Shader = diffuseShader 157 | e.Node.Color = mgl.Vec4{1.0, 1.0, 1.0, 1.0} 158 | e.Node.Location = mgl.Vec3{float32(i*2.0-cubesToMake/2) - 0.5 + offset, 10.0, 0.0} 159 | e.Node.Tex0 = crateTexture 160 | 161 | // create the collision box for the the cube 162 | cubeCollider := cubez.NewCollisionCube(nil, m.Vector3{0.5, 0.5, 0.5}) 163 | cubeCollider.Body.Position = m.Vector3{m.Real(i*2.0-cubesToMake/2) - 0.5 + m.Real(offset), 10.0, 0.0} 164 | cubeCollider.Body.SetMass(8.0) 165 | cubeCollider.Body.CanSleep = true 166 | 167 | var cubeInertia m.Matrix3 168 | cubeInertia.SetBlockInertiaTensor(&cubeCollider.HalfSize, 8.0) 169 | cubeCollider.Body.SetInertiaTensor(&cubeInertia) 170 | 171 | cubeCollider.Body.CalculateDerivedData() 172 | cubeCollider.CalculateDerivedData() 173 | e.Collider = cubeCollider 174 | 175 | cubes = append(cubes, e) 176 | } 177 | } 178 | 179 | func keyCallback(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 180 | // Key W == close app 181 | if key == glfw.KeyEscape && action == glfw.Press { 182 | w.SetShouldClose(true) 183 | } 184 | if key == glfw.KeySpace && action == glfw.Press { 185 | fire() 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /examples/exampleapp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package main 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "math" 10 | "runtime" 11 | "strings" 12 | "time" 13 | 14 | "image" 15 | "image/draw" 16 | "image/png" 17 | "os" 18 | 19 | gl "github.com/go-gl/gl/v3.3-core/gl" 20 | glfw "github.com/go-gl/glfw/v3.1/glfw" 21 | mgl "github.com/go-gl/mathgl/mgl32" 22 | "github.com/tbogdala/cubez" 23 | m "github.com/tbogdala/cubez/math" 24 | ) 25 | 26 | var ( 27 | // UnlitColorVertShader is a basic color vertex shader 28 | UnlitColorVertShader = `#version 330 29 | uniform mat4 MVP_MATRIX; 30 | uniform vec4 MATERIAL_DIFFUSE; 31 | in vec3 VERTEX_POSITION; 32 | out vec4 vs_diffuse; 33 | 34 | void main() 35 | { 36 | vs_diffuse = MATERIAL_DIFFUSE; 37 | gl_Position = MVP_MATRIX * vec4(VERTEX_POSITION, 1.0); 38 | }` 39 | 40 | // UnlitColorFragShader is a basic color fragment shader 41 | UnlitColorFragShader = `#version 330 42 | in vec4 vs_diffuse; 43 | out vec4 colourOut; 44 | void main() 45 | { 46 | colourOut = vs_diffuse; 47 | }` 48 | 49 | DiffuseColorVertShader = `#version 330 50 | precision highp float; 51 | 52 | uniform mat4 MVP_MATRIX; 53 | uniform mat4 MV_MATRIX; 54 | in vec3 VERTEX_POSITION; 55 | in vec3 VERTEX_NORMAL; 56 | 57 | out vec3 vs_position; 58 | out vec3 vs_eye_normal; 59 | 60 | void main() 61 | { 62 | vs_position = VERTEX_POSITION; 63 | vs_eye_normal = normalize(mat3(MV_MATRIX) * VERTEX_NORMAL); 64 | gl_Position = MVP_MATRIX * vec4(VERTEX_POSITION, 1.0); 65 | }` 66 | 67 | DiffuseColorFragShader = `#version 330 68 | precision highp float; 69 | 70 | uniform mat4 MV_MATRIX; 71 | uniform mat4 MVP_MATRIX; 72 | uniform vec4 MATERIAL_DIFFUSE; 73 | 74 | in vec3 vs_position; 75 | in vec3 vs_eye_normal; 76 | 77 | out vec4 frag_color; 78 | 79 | void main() 80 | { 81 | vec4 LIGHT_POSITION = vec4(-100.0, 100.0, 100.0, 1.0); 82 | vec4 LIGHT_DIFFUSE = vec4(1.0); 83 | 84 | vec4 eye_vertex = MV_MATRIX * vec4(vs_position, 1.0); 85 | vec3 s = normalize(vec3(LIGHT_POSITION-eye_vertex)); 86 | float diffuseIntensity = max(dot(s,vs_eye_normal), 0.0); 87 | 88 | 89 | frag_color = MATERIAL_DIFFUSE * diffuseIntensity; 90 | }` 91 | 92 | DiffuseTextureVertShader = `#version 330 93 | precision highp float; 94 | 95 | uniform mat4 MVP_MATRIX; 96 | uniform mat4 MV_MATRIX; 97 | in vec3 VERTEX_POSITION; 98 | in vec3 VERTEX_NORMAL; 99 | in vec2 VERTEX_UV_0; 100 | 101 | out vec3 vs_position; 102 | out vec3 vs_eye_normal; 103 | out vec2 vs_uv_0; 104 | 105 | void main() 106 | { 107 | vs_position = VERTEX_POSITION; 108 | vs_uv_0 = VERTEX_UV_0; 109 | vs_eye_normal = normalize(mat3(MV_MATRIX) * VERTEX_NORMAL); 110 | gl_Position = MVP_MATRIX * vec4(VERTEX_POSITION, 1.0); 111 | }` 112 | 113 | DiffuseTextureFragShader = `#version 330 114 | precision highp float; 115 | uniform sampler2D MATERIAL_TEX_0; 116 | 117 | uniform mat4 MV_MATRIX; 118 | uniform mat4 MVP_MATRIX; 119 | uniform vec4 MATERIAL_DIFFUSE; 120 | 121 | in vec3 vs_position; 122 | in vec3 vs_eye_normal; 123 | in vec2 vs_uv_0; 124 | 125 | out vec4 frag_color; 126 | 127 | void main() 128 | { 129 | vec4 LIGHT_POSITION = vec4(-100.0, 100.0, 100.0, 1.0); 130 | vec4 LIGHT_DIFFUSE = vec4(1.0); 131 | 132 | vec4 eye_vertex = MV_MATRIX * vec4(vs_position, 1.0); 133 | vec3 s = normalize(vec3(LIGHT_POSITION-eye_vertex)); 134 | float diffuseIntensity = max(dot(s,vs_eye_normal), 0.0); 135 | 136 | 137 | frag_color = MATERIAL_DIFFUSE * texture(MATERIAL_TEX_0, vs_uv_0) * diffuseIntensity; 138 | }` 139 | ) 140 | 141 | // GLFW event handling must run on the main OS thread 142 | func init() { 143 | runtime.LockOSThread() 144 | } 145 | 146 | // SetGlVector3 copies the values from one math library vector to another. 147 | func SetGlVector3(dst *mgl.Vec3, src *m.Vector3) { 148 | dst[0] = float32(src[0]) 149 | dst[1] = float32(src[1]) 150 | dst[2] = float32(src[2]) 151 | } 152 | 153 | // SetGlQuat copies the values from one math library vector to another. 154 | func SetGlQuat(dst *mgl.Quat, src *m.Quat) { 155 | dst.W = float32(src[0]) 156 | dst.V[0] = float32(src[1]) 157 | dst.V[1] = float32(src[2]) 158 | dst.V[2] = float32(src[3]) 159 | } 160 | 161 | type Entity struct { 162 | Node *Renderable 163 | Collider cubez.Collider 164 | } 165 | 166 | func NewEntity(node *Renderable, collider cubez.Collider) *Entity { 167 | e := new(Entity) 168 | e.Node = node 169 | e.Collider = collider 170 | return e 171 | } 172 | 173 | type RenderLoopCallback func(delta float64) 174 | 175 | // ExampleApp is a type representing the example application and holds on 176 | // to related data like OpenGL windows. 177 | type ExampleApp struct { 178 | // MainWindow is the main OpenGL window for the application 179 | MainWindow *glfw.Window 180 | 181 | // Width is how wide the app window is 182 | Width int 183 | 184 | // Height is how tall the app window is 185 | Height int 186 | 187 | // CameraPos is the position of the camera in world space 188 | CameraPos mgl.Vec3 189 | 190 | // CameraRotation is a quaternion representing the direction 191 | // the camera is looking. 192 | CameraRotation mgl.Quat 193 | 194 | // OnUpdate is called just prior to OnRender and can be used to update 195 | // the application data. 196 | OnUpdate RenderLoopCallback 197 | 198 | // OnRender is called at the end of the render loop and is meant to be 199 | // the spot where the application renders the objects to OpenGL. 200 | OnRender RenderLoopCallback 201 | } 202 | 203 | // NewApp returns a new ExampleApp object to control the display of the example app. 204 | func NewApp() *ExampleApp { 205 | app := new(ExampleApp) 206 | app.CameraRotation = mgl.QuatIdent() 207 | return app 208 | } 209 | 210 | // InitGraphics creates an OpenGL window and initializes the required graphics libraries. 211 | // It will either succeed or panic. 212 | func (app *ExampleApp) InitGraphics(title string, w int, h int) { 213 | err := glfw.Init() 214 | if err != nil { 215 | panic("Can't init glfw! " + err.Error()) 216 | } 217 | 218 | // request a OpenGL 3.3 core context 219 | glfw.WindowHint(glfw.Samples, 0) 220 | glfw.WindowHint(glfw.ContextVersionMajor, 3) 221 | glfw.WindowHint(glfw.ContextVersionMinor, 3) 222 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 223 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 224 | 225 | // do the actual window creation 226 | app.MainWindow, err = glfw.CreateWindow(w, h, title, nil, nil) 227 | if err != nil { 228 | panic("Failed to create the main window! " + err.Error()) 229 | } 230 | app.MainWindow.MakeContextCurrent() 231 | glfw.SwapInterval(0) 232 | 233 | // make sure that all of the GL functions are initialized 234 | err = gl.Init() 235 | if err != nil { 236 | panic("Failed to initialize GL! " + err.Error()) 237 | } 238 | 239 | // set the app window dimensions 240 | app.Width = w 241 | app.Height = h 242 | 243 | gl.FrontFace(gl.CCW) 244 | gl.CullFace(gl.BACK) 245 | gl.Enable(gl.CULL_FACE) 246 | } 247 | 248 | // Terminate closes the OpenGL window and unloads the graphics libraries. 249 | func (app *ExampleApp) Terminate() { 250 | app.MainWindow.SetShouldClose(true) 251 | glfw.Terminate() 252 | } 253 | 254 | // SetKeyCallback sets a key handler for the main window. 255 | func (app *ExampleApp) SetKeyCallback(cb glfw.KeyCallback) { 256 | app.MainWindow.SetKeyCallback(cb) 257 | } 258 | 259 | var ( 260 | // keeps track of the start of the last render loop 261 | lastRenderTime time.Time 262 | ) 263 | 264 | // RenderLoop is the main render loop for the application 265 | func (app *ExampleApp) RenderLoop() { 266 | lastRenderTime = time.Now() 267 | 268 | for !app.MainWindow.ShouldClose() { 269 | // get the time delta 270 | loopTime := time.Now() 271 | deltaNano := loopTime.Sub(lastRenderTime).Nanoseconds() 272 | deltaF := float64(deltaNano) * (1.0 / float64(time.Second)) 273 | 274 | // call the Update callback 275 | if app.OnUpdate != nil { 276 | app.OnUpdate(deltaF) 277 | } 278 | 279 | // call the Render callback 280 | if app.OnRender != nil { 281 | app.OnRender(deltaF) 282 | } 283 | 284 | // draw the screen and get any input 285 | app.MainWindow.SwapBuffers() 286 | glfw.PollEvents() 287 | 288 | // update the last render time 289 | lastRenderTime = loopTime 290 | } 291 | } 292 | 293 | // Renderable is an object that can be drawn in the render loop 294 | type Renderable struct { 295 | // Shader is the shader program to use to draw the renderable 296 | Shader uint32 297 | 298 | // Tex0 is the first texture to be bound to the shader 299 | Tex0 uint32 300 | 301 | // Color is a material color for the object passed to the shader when drawn. 302 | Color mgl.Vec4 303 | 304 | // Vao is the VAO object used to draw the object 305 | Vao uint32 306 | 307 | // VertVBO is the VBO that holds the vertex data 308 | VertVBO uint32 309 | 310 | // UvVBO is the VBO that holds the UV data 311 | UvVBO uint32 312 | 313 | // NormsVBO is the VBO that hold the normal data 314 | NormsVBO uint32 315 | 316 | // ElementsVBO is the VBO 317 | ElementsVBO uint32 318 | 319 | // FaceCount is the number of faces to draw for the object 320 | FaceCount int 321 | 322 | // Scale represents how to scale the object when drawing 323 | Scale mgl.Vec3 324 | 325 | // Location positions the object in world space 326 | Location mgl.Vec3 327 | 328 | // Rotation is the rotation of the object in world space 329 | Rotation mgl.Quat 330 | 331 | // LocalRotation is rotation applied to the object in local space 332 | LocalRotation mgl.Quat 333 | } 334 | 335 | // NewRenderable creates a new Renderable object. 336 | func NewRenderable() *Renderable { 337 | r := new(Renderable) 338 | r.Scale = mgl.Vec3{1.0, 1.0, 1.0} 339 | return r 340 | } 341 | 342 | // GetTransformMat4 creates a transform matrix: scale * transform 343 | func (r *Renderable) GetTransformMat4() mgl.Mat4 { 344 | scaleMat := mgl.Scale3D(r.Scale[0], r.Scale[1], r.Scale[2]) 345 | transMat := mgl.Translate3D(r.Location[0], r.Location[1], r.Location[2]) 346 | localRotMat := r.LocalRotation.Mat4() 347 | rotMat := r.Rotation.Mat4() 348 | modelTransform := rotMat.Mul4(transMat).Mul4(localRotMat).Mul4(scaleMat) 349 | return modelTransform 350 | } 351 | 352 | func (r *Renderable) Draw(perspective mgl.Mat4, view mgl.Mat4) { 353 | gl.UseProgram(r.Shader) 354 | gl.BindVertexArray(r.Vao) 355 | 356 | model := r.GetTransformMat4() 357 | 358 | var mvp mgl.Mat4 359 | shaderMvp := getUniformLocation(r.Shader, "MVP_MATRIX") 360 | if shaderMvp >= 0 { 361 | mvp = perspective.Mul4(view).Mul4(model) 362 | gl.UniformMatrix4fv(shaderMvp, 1, false, &mvp[0]) 363 | } 364 | 365 | shaderMv := getUniformLocation(r.Shader, "MV_MATRIX") 366 | if shaderMv >= 0 { 367 | mv := view.Mul4(model) 368 | gl.UniformMatrix4fv(shaderMv, 1, false, &mv[0]) 369 | } 370 | 371 | shaderTex0 := getUniformLocation(r.Shader, "DIFFUSE_TEX") 372 | if shaderTex0 >= 0 { 373 | gl.ActiveTexture(gl.TEXTURE0) 374 | gl.BindTexture(gl.TEXTURE_2D, r.Tex0) 375 | gl.Uniform1i(shaderTex0, 0) 376 | } 377 | 378 | shaderColor := getUniformLocation(r.Shader, "MATERIAL_DIFFUSE") 379 | if shaderColor >= 0 { 380 | gl.Uniform4f(shaderColor, r.Color[0], r.Color[1], r.Color[2], r.Color[3]) 381 | } 382 | 383 | shaderTex1 := getUniformLocation(r.Shader, "MATERIAL_TEX_0") 384 | if shaderTex1 >= 0 { 385 | gl.ActiveTexture(gl.TEXTURE0) 386 | gl.BindTexture(gl.TEXTURE_2D, r.Tex0) 387 | gl.Uniform1i(shaderTex1, 0) 388 | } 389 | 390 | shaderCameraWorldPos := getUniformLocation(r.Shader, "CAMERA_WORLD_POSITION") 391 | if shaderCameraWorldPos >= 0 { 392 | gl.Uniform3f(shaderCameraWorldPos, -view[12], -view[13], -view[14]) 393 | } 394 | 395 | shaderPosition := getAttribLocation(r.Shader, "VERTEX_POSITION") 396 | if shaderPosition >= 0 { 397 | gl.BindBuffer(gl.ARRAY_BUFFER, r.VertVBO) 398 | gl.EnableVertexAttribArray(uint32(shaderPosition)) 399 | gl.VertexAttribPointer(uint32(shaderPosition), 3, gl.FLOAT, false, 0, gl.PtrOffset(0)) 400 | } 401 | 402 | shaderNormal := getAttribLocation(r.Shader, "VERTEX_NORMAL") 403 | if shaderNormal >= 0 { 404 | gl.BindBuffer(gl.ARRAY_BUFFER, r.NormsVBO) 405 | gl.EnableVertexAttribArray(uint32(shaderNormal)) 406 | gl.VertexAttribPointer(uint32(shaderNormal), 3, gl.FLOAT, false, 0, gl.PtrOffset(0)) 407 | } 408 | 409 | shaderVertUv := getAttribLocation(r.Shader, "VERTEX_UV_0") 410 | if shaderVertUv >= 0 { 411 | gl.BindBuffer(gl.ARRAY_BUFFER, r.UvVBO) 412 | gl.EnableVertexAttribArray(uint32(shaderVertUv)) 413 | gl.VertexAttribPointer(uint32(shaderVertUv), 2, gl.FLOAT, false, 0, gl.PtrOffset(0)) 414 | } 415 | 416 | gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.ElementsVBO) 417 | gl.DrawElements(gl.TRIANGLES, int32(r.FaceCount*3), gl.UNSIGNED_INT, gl.PtrOffset(0)) 418 | gl.BindVertexArray(0) 419 | } 420 | 421 | // setup a cache for the uniform and attribute getter functions 422 | var ( 423 | uniCache = make(map[string]int32) 424 | attrCache = make(map[string]int32) 425 | ) 426 | 427 | func getUniformLocation(prog uint32, name string) int32 { 428 | // attempt to get it from the cache first 429 | ul, found := uniCache[name] 430 | if found { 431 | return ul 432 | } 433 | 434 | // pull the location from the shader and cache it 435 | uniGLName := name + "\x00" 436 | ul = gl.GetUniformLocation(prog, gl.Str(uniGLName)) 437 | 438 | // cache even if it returns -1 so that it doesn't repeatedly check 439 | uniCache[name] = ul 440 | return ul 441 | } 442 | 443 | func getAttribLocation(prog uint32, name string) int32 { 444 | // attempt to get it from the cache first 445 | al, found := attrCache[name] 446 | if found { 447 | return al 448 | } 449 | 450 | // pull the location from the shader and cache it 451 | attrGLName := name + "\x00" 452 | al = gl.GetAttribLocation(prog, gl.Str(attrGLName)) 453 | 454 | // cache even if it returns -1 so that it doesn't repeatedly check 455 | attrCache[name] = al 456 | return al 457 | } 458 | 459 | // LoadShaderProgram loads shader objects and then attaches them to a program 460 | func LoadShaderProgram(vertShader, fragShader string) (uint32, error) { 461 | // create the program 462 | prog := gl.CreateProgram() 463 | 464 | // create the vertex shader 465 | vs := gl.CreateShader(gl.VERTEX_SHADER) 466 | cVertShader, free := gl.Strs(vertShader + "\x00") 467 | gl.ShaderSource(vs, 1, cVertShader, nil) 468 | gl.CompileShader(vs) 469 | free() 470 | 471 | var status int32 472 | gl.GetShaderiv(vs, gl.COMPILE_STATUS, &status) 473 | if status == gl.FALSE { 474 | var logLength int32 475 | gl.GetShaderiv(vs, gl.INFO_LOG_LENGTH, &logLength) 476 | log := strings.Repeat("\x00", int(logLength+1)) 477 | gl.GetShaderInfoLog(vs, logLength, nil, gl.Str(log)) 478 | 479 | err := fmt.Sprintf("Failed to compile the vertex shader!\n%s", log) 480 | fmt.Println(err) 481 | return 0, errors.New(err) 482 | } 483 | defer gl.DeleteShader(vs) 484 | 485 | // create the fragment shader 486 | fs := gl.CreateShader(gl.FRAGMENT_SHADER) 487 | cFragShader, free := gl.Strs(fragShader + "\x00") 488 | gl.ShaderSource(fs, 1, cFragShader, nil) 489 | gl.CompileShader(fs) 490 | free() 491 | 492 | gl.GetShaderiv(fs, gl.COMPILE_STATUS, &status) 493 | if status == gl.FALSE { 494 | var logLength int32 495 | gl.GetShaderiv(fs, gl.INFO_LOG_LENGTH, &logLength) 496 | log := strings.Repeat("\x00", int(logLength+1)) 497 | gl.GetShaderInfoLog(fs, logLength, nil, gl.Str(log)) 498 | 499 | err := fmt.Sprintf("Failed to compile the fragment shader!\n%s", log) 500 | fmt.Println(err) 501 | return 0, errors.New(err) 502 | } 503 | defer gl.DeleteShader(fs) 504 | 505 | // attach the shaders to the program and link 506 | // attach the shaders to the program and link 507 | gl.AttachShader(prog, vs) 508 | gl.AttachShader(prog, fs) 509 | gl.LinkProgram(prog) 510 | 511 | gl.GetProgramiv(prog, gl.LINK_STATUS, &status) 512 | if status == gl.FALSE { 513 | var logLength int32 514 | gl.GetProgramiv(prog, gl.INFO_LOG_LENGTH, &logLength) 515 | log := strings.Repeat("\x00", int(logLength+1)) 516 | gl.GetProgramInfoLog(prog, logLength, nil, gl.Str(log)) 517 | 518 | error := fmt.Sprintf("Failed to link the program!\n%s", log) 519 | fmt.Println(error) 520 | return 0, errors.New(error) 521 | } 522 | 523 | return prog, nil 524 | } 525 | 526 | func loadFile(filePath string) (rgba_flipped *image.NRGBA, e error) { 527 | imgFile, err := os.Open(filePath) 528 | if err != nil { 529 | return nil, fmt.Errorf("Failed to open the texture file: %v\n", err) 530 | } 531 | 532 | img, err := png.Decode(imgFile) 533 | imgFile.Close() 534 | if err != nil { 535 | return nil, fmt.Errorf("Failed to decode the texture: %v\n", err) 536 | } 537 | 538 | // if the source image doesn't have alpha, set it manually 539 | b := img.Bounds() 540 | rgba := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy())) 541 | draw.Draw(rgba, rgba.Bounds(), img, b.Min, draw.Src) 542 | 543 | // flip the image vertically 544 | rows := b.Max.Y 545 | rgba_flipped = image.NewNRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y)) 546 | for dy := 0; dy < rows; dy++ { 547 | sy := b.Max.Y - dy - 1 548 | for dx := 0; dx < b.Max.X; dx++ { 549 | soffset := sy*rgba.Stride + dx*4 550 | doffset := dy*rgba_flipped.Stride + dx*4 551 | copy(rgba_flipped.Pix[doffset:doffset+4], rgba.Pix[soffset:soffset+4]) 552 | } 553 | } 554 | 555 | return rgba_flipped, nil 556 | } 557 | 558 | func LoadImageToTexture(filePath string) (glTex uint32, e error) { 559 | gl.GenTextures(1, &glTex) 560 | gl.ActiveTexture(gl.TEXTURE0) 561 | gl.BindTexture(gl.TEXTURE_2D, glTex) 562 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR_MIPMAP_LINEAR) 563 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) 564 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT) 565 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT) 566 | 567 | rgba_flipped, err := loadFile(filePath) 568 | if err != nil { 569 | return glTex, err 570 | } 571 | 572 | imageSize := int32(rgba_flipped.Bounds().Max.X) 573 | 574 | gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, imageSize, imageSize, 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(rgba_flipped.Pix)) 575 | gl.GenerateMipmap(gl.TEXTURE_2D) 576 | return glTex, nil 577 | } 578 | 579 | // CreateCube makes a new Renderable object with the specified dimensions for the cube. 580 | func CreateCube(xmin, ymin, zmin, xmax, ymax, zmax float32) *Renderable { 581 | /* Cube vertices are layed out like this: 582 | 583 | +--------+ 6 5 584 | / | /| 585 | +--------+ | 1 0 +Y 586 | | | | | |___ +X 587 | | +------|-+ 7 4 / 588 | |/ |/ +Z 589 | +--------+ 2 3 590 | 591 | */ 592 | 593 | verts := [...]float32{ 594 | xmax, ymax, zmax, xmin, ymax, zmax, xmin, ymin, zmax, xmax, ymin, zmax, // v0,v1,v2,v3 (front) 595 | xmax, ymax, zmin, xmax, ymax, zmax, xmax, ymin, zmax, xmax, ymin, zmin, // v5,v0,v3,v4 (right) 596 | xmax, ymax, zmin, xmin, ymax, zmin, xmin, ymax, zmax, xmax, ymax, zmax, // v5,v6,v1,v0 (top) 597 | xmin, ymax, zmax, xmin, ymax, zmin, xmin, ymin, zmin, xmin, ymin, zmax, // v1,v6,v7,v2 (left) 598 | xmax, ymin, zmax, xmin, ymin, zmax, xmin, ymin, zmin, xmax, ymin, zmin, // v3,v2,v7,v4 (bottom) 599 | xmin, ymax, zmin, xmax, ymax, zmin, xmax, ymin, zmin, xmin, ymin, zmin, // v6,v5,v4,v7 (back) 600 | } 601 | indexes := [...]uint32{ 602 | 0, 1, 2, 2, 3, 0, 603 | 4, 5, 6, 6, 7, 4, 604 | 8, 9, 10, 10, 11, 8, 605 | 12, 13, 14, 14, 15, 12, 606 | 16, 17, 18, 18, 19, 16, 607 | 20, 21, 22, 22, 23, 20, 608 | } 609 | uvs := [...]float32{ 610 | 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 611 | 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 612 | 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 613 | 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 614 | 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 615 | 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 616 | } 617 | normals := [...]float32{ 618 | 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0,v1,v2,v3 (front) 619 | 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v5,v0,v3,v4 (right) 620 | 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // v5,v6,v1,v0 (top) 621 | -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1,v6,v7,v2 (left) 622 | 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, // v3,v2,v7,v4 (bottom) 623 | 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, // v6,v5,v4,v7 (back) 624 | } 625 | 626 | r := NewRenderable() 627 | gl.GenVertexArrays(1, &r.Vao) 628 | r.FaceCount = 12 629 | 630 | const floatSize = 4 631 | const uintSize = 4 632 | 633 | // create a VBO to hold the vertex data 634 | gl.GenBuffers(1, &r.VertVBO) 635 | gl.BindBuffer(gl.ARRAY_BUFFER, r.VertVBO) 636 | gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(verts), gl.Ptr(&verts[0]), gl.STATIC_DRAW) 637 | 638 | // create a VBO to hold the uv data 639 | gl.GenBuffers(1, &r.UvVBO) 640 | gl.BindBuffer(gl.ARRAY_BUFFER, r.UvVBO) 641 | gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(uvs), gl.Ptr(&uvs[0]), gl.STATIC_DRAW) 642 | 643 | // create a VBO to hold the normals data 644 | gl.GenBuffers(1, &r.NormsVBO) 645 | gl.BindBuffer(gl.ARRAY_BUFFER, r.NormsVBO) 646 | gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(normals), gl.Ptr(&normals[0]), gl.STATIC_DRAW) 647 | 648 | // create a VBO to hold the face indexes 649 | gl.GenBuffers(1, &r.ElementsVBO) 650 | gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.ElementsVBO) 651 | gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, uintSize*len(indexes), gl.Ptr(&indexes[0]), gl.STATIC_DRAW) 652 | 653 | return r 654 | } 655 | 656 | // CreateSphere generates a 3d uv-sphere with the given radius and returns a Renderable. 657 | func CreateSphere(radius float32, rings int, sectors int) *Renderable { 658 | // nothing to create 659 | if rings < 2 || sectors < 2 { 660 | return nil 661 | } 662 | 663 | const piDiv2 = math.Pi / 2.0 664 | 665 | verts := make([]float32, 0, rings*sectors) 666 | indexes := make([]uint32, 0, rings*sectors) 667 | uvs := make([]float32, 0, rings*sectors) 668 | normals := make([]float32, 0, rings*sectors) 669 | 670 | R := float64(1.0 / float32(rings-1)) 671 | S := float64(1.0 / float32(sectors-1)) 672 | 673 | for ri := 0; ri < int(rings); ri++ { 674 | for si := 0; si < int(sectors); si++ { 675 | y := float32(math.Sin(-piDiv2 + math.Pi*float64(ri)*R)) 676 | x := float32(math.Cos(2.0*math.Pi*float64(si)*S) * math.Sin(math.Pi*float64(ri)*R)) 677 | z := float32(math.Sin(2.0*math.Pi*float64(si)*S) * math.Sin(math.Pi*float64(ri)*R)) 678 | 679 | uvs = append(uvs, float32(si)*float32(S)) 680 | uvs = append(uvs, float32(ri)*float32(R)) 681 | 682 | verts = append(verts, x*radius) 683 | verts = append(verts, y*radius) 684 | verts = append(verts, z*radius) 685 | 686 | normals = append(normals, x) 687 | normals = append(normals, y) 688 | normals = append(normals, z) 689 | 690 | currentRow := ri * sectors 691 | nextRow := (ri + 1) * sectors 692 | 693 | indexes = append(indexes, uint32(currentRow+si)) 694 | indexes = append(indexes, uint32(nextRow+si)) 695 | indexes = append(indexes, uint32(nextRow+si+1)) 696 | 697 | indexes = append(indexes, uint32(currentRow+si)) 698 | indexes = append(indexes, uint32(nextRow+si+1)) 699 | indexes = append(indexes, uint32(currentRow+si+1)) 700 | } 701 | } 702 | 703 | r := NewRenderable() 704 | gl.GenVertexArrays(1, &r.Vao) 705 | r.FaceCount = rings * sectors * 2 706 | 707 | const floatSize = 4 708 | const uintSize = 4 709 | 710 | // create a VBO to hold the vertex data 711 | gl.GenBuffers(1, &r.VertVBO) 712 | gl.BindBuffer(gl.ARRAY_BUFFER, r.VertVBO) 713 | gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(verts), gl.Ptr(&verts[0]), gl.STATIC_DRAW) 714 | 715 | // create a VBO to hold the uv data 716 | gl.GenBuffers(1, &r.UvVBO) 717 | gl.BindBuffer(gl.ARRAY_BUFFER, r.UvVBO) 718 | gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(uvs), gl.Ptr(&uvs[0]), gl.STATIC_DRAW) 719 | 720 | // create a VBO to hold the normals data 721 | gl.GenBuffers(1, &r.NormsVBO) 722 | gl.BindBuffer(gl.ARRAY_BUFFER, r.NormsVBO) 723 | gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(normals), gl.Ptr(&normals[0]), gl.STATIC_DRAW) 724 | 725 | // create a VBO to hold the face indexes 726 | gl.GenBuffers(1, &r.ElementsVBO) 727 | gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.ElementsVBO) 728 | gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, uintSize*len(indexes), gl.Ptr(&indexes[0]), gl.STATIC_DRAW) 729 | 730 | return r 731 | } 732 | 733 | // CreatePlaneXZ makes a 2d Renderable object on the XZ plane for the given size, 734 | // where (x0,z0) is the lower left and (x1, z1) is the upper right coordinate. 735 | func CreatePlaneXZ(x0, z0, x1, z1 float32, scaleUVs float32) *Renderable { 736 | verts := [12]float32{ 737 | x0, 0.0, z0, 738 | x1, 0.0, z0, 739 | x0, 0.0, z1, 740 | x1, 0.0, z1, 741 | } 742 | indexes := [6]uint32{ 743 | 0, 1, 2, 744 | 1, 3, 2, 745 | } 746 | uvs := [8]float32{ 747 | 0.0, 0.0, 748 | scaleUVs, 0.0, 749 | 0.0, scaleUVs, 750 | scaleUVs, scaleUVs, 751 | } 752 | 753 | normals := [12]float32{ 754 | 0.0, 1.0, 0.0, 755 | 0.0, 1.0, 0.0, 756 | 0.0, 1.0, 0.0, 757 | 0.0, 1.0, 0.0, 758 | } 759 | 760 | return createPlane(x0, z0, x1, z1, verts, indexes, uvs, normals) 761 | } 762 | 763 | func createPlane(x0, y0, x1, y1 float32, verts [12]float32, indexes [6]uint32, uvs [8]float32, normals [12]float32) *Renderable { 764 | const floatSize = 4 765 | const uintSize = 4 766 | 767 | r := NewRenderable() 768 | gl.GenVertexArrays(1, &r.Vao) 769 | r.FaceCount = 2 770 | 771 | // create a VBO to hold the vertex data 772 | gl.GenBuffers(1, &r.VertVBO) 773 | gl.BindBuffer(gl.ARRAY_BUFFER, r.VertVBO) 774 | gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(verts), gl.Ptr(&verts[0]), gl.STATIC_DRAW) 775 | 776 | // create a VBO to hold the uv data 777 | gl.GenBuffers(1, &r.UvVBO) 778 | gl.BindBuffer(gl.ARRAY_BUFFER, r.UvVBO) 779 | gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(uvs), gl.Ptr(&uvs[0]), gl.STATIC_DRAW) 780 | 781 | // create a VBO to hold the normals data 782 | gl.GenBuffers(1, &r.NormsVBO) 783 | gl.BindBuffer(gl.ARRAY_BUFFER, r.NormsVBO) 784 | gl.BufferData(gl.ARRAY_BUFFER, floatSize*len(normals), gl.Ptr(&normals[0]), gl.STATIC_DRAW) 785 | 786 | // create a VBO to hold the face indexes 787 | gl.GenBuffers(1, &r.ElementsVBO) 788 | gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.ElementsVBO) 789 | gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, uintSize*len(indexes), gl.Ptr(&indexes[0]), gl.STATIC_DRAW) 790 | 791 | return r 792 | } 793 | -------------------------------------------------------------------------------- /examples/screenshots/ballistic-150912.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbogdala/cubez/e41617e9e489bbe27bf1e9e35e0225e4bf36d609/examples/screenshots/ballistic-150912.jpg -------------------------------------------------------------------------------- /examples/screenshots/cubedrop-150912.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbogdala/cubez/e41617e9e489bbe27bf1e9e35e0225e4bf36d609/examples/screenshots/cubedrop-150912.jpg -------------------------------------------------------------------------------- /math/math.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | /* 5 | 6 | The math module of this project defines the floating point precision to 7 | be used and the mathematical types to be used. 8 | 9 | From there it also defines mathematial operations on vectors, matrixes 10 | and quaternions that operate by reference. 11 | 12 | All matrixes will be created in column-major ordering. 13 | 14 | */ 15 | 16 | package math 17 | 18 | import ( 19 | "math" 20 | ) 21 | 22 | // Real is the type of float used in the library. 23 | type Real float64 24 | 25 | // Espilon is used to test equality of the floats and represents how 26 | // close two Reals can be and still test positive for equality. 27 | const Epsilon Real = 1e-7 28 | 29 | const ( 30 | MinNormal = Real(1.1754943508222875e-38) // 1 / 2**(127 - 1) 31 | MinValue = Real(math.SmallestNonzeroFloat64) 32 | MaxValue = Real(math.MaxFloat64) 33 | ) 34 | 35 | var ( 36 | InfPos = Real(math.Inf(1)) 37 | InfNeg = Real(math.Inf(-1)) 38 | NaN = Real(math.NaN()) 39 | ) 40 | 41 | // Matrix3 is a 3x3 matrix of floats in column-major order. 42 | type Matrix3 [9]Real 43 | 44 | // Matrix3x4 is a 3x4 matrix of floats in column-major order 45 | // that can be used to hold a rotation and translation in 3D space 46 | // where the 4th row would have been [0 0 0 1] 47 | type Matrix3x4 [12]Real 48 | 49 | // Matrix4 is a 4x4 matrix of floats in column-major order. 50 | type Matrix4 [16]Real 51 | 52 | // Vector3 is a vector of three floats. 53 | type Vector3 [3]Real 54 | 55 | // Vector4 is a vector of four floats. 56 | type Vector4 [4]Real 57 | 58 | // Quat is the type of a quaternion (w,x,y,z). 59 | type Quat Vector4 60 | 61 | // RealEqual tests the two Real numbers for equality, which really means 62 | // that it tests whether or not the difference between the two is 63 | // smaller than Epsilon. 64 | func RealEqual(a, b Real) bool { 65 | // handle cases like inf 66 | if a == b { 67 | return true 68 | } 69 | 70 | diff := Real(math.Abs(float64(a - b))) 71 | 72 | // if a or b is 0 or really close to it 73 | if a*b == 0 || diff < MinNormal { 74 | return diff < Epsilon*Epsilon 75 | } 76 | 77 | return diff/Real(math.Abs(float64(a))+math.Abs(float64(b))) < Epsilon 78 | } 79 | 80 | // DegToRad converts degrees to radians 81 | func DegToRad(angle Real) Real { 82 | return angle * math.Pi / 180.0 83 | } 84 | 85 | // RadToDeg converts radians to degrees 86 | func RadToDeg(angle Real) Real { 87 | return angle * 180.0 / math.Pi 88 | } 89 | 90 | // RealAbs is an absolute value wrapper for the Real type. 91 | func RealAbs(a Real) Real { 92 | return Real(math.Abs(float64(a))) 93 | } 94 | 95 | // RealSqrt is a square root wrapper for the Real type. 96 | func RealSqrt(a Real) Real { 97 | return Real(math.Sqrt(float64(a))) 98 | } 99 | 100 | // RealSin is a sin function wrapper for the Real type. 101 | func RealSin(a Real) Real { 102 | return Real(math.Sin(float64(a))) 103 | } 104 | 105 | // RealCos is a cos function wrapper for the Real type. 106 | func RealCos(a Real) Real { 107 | return Real(math.Cos(float64(a))) 108 | } 109 | 110 | // RealIsNaN returns true if the value is Not a Number. 111 | func RealIsNaN(a Real) bool { 112 | return math.IsNaN(float64(a)) 113 | } 114 | -------------------------------------------------------------------------------- /math/matrix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package math 5 | 6 | // Sanity conversion charts between different ordering: 7 | // 8 | // col major row major col maj 4x4 row major col major 9 | // 0 3 6 9 0 1 2 3 0 4 8 12 0 1 2 0 3 6 10 | // 1 4 7 10 <= 4 5 6 7 1 5 9 13 3 4 5 => 1 4 7 11 | // 2 5 8 11 8 9 10 11 2 6 10 14 6 7 8 2 5 8 12 | // 3 7 11 15 13 | 14 | // SetIdentity loads the matrix with its identity. 15 | func (m *Matrix3) SetIdentity() { 16 | m[0], m[3], m[6] = 1.0, 0.0, 0.0 17 | m[1], m[4], m[7] = 0.0, 1.0, 0.0 18 | m[2], m[5], m[8] = 0.0, 0.0, 1.0 19 | } 20 | 21 | // SetIdentity loads the matrix with its identity. 22 | func (m *Matrix3x4) SetIdentity() { 23 | m[0], m[3], m[6], m[9] = 1.0, 0.0, 0.0, 0.0 24 | m[1], m[4], m[7], m[10] = 0.0, 1.0, 0.0, 0.0 25 | m[2], m[5], m[8], m[11] = 0.0, 0.0, 1.0, 0.0 26 | } 27 | 28 | // SetIdentity loads the matrix with its identity. 29 | func (m *Matrix4) SetIdentity() { 30 | m[0], m[4], m[8], m[12] = 1.0, 0.0, 0.0, 0.0 31 | m[1], m[5], m[9], m[13] = 0.0, 1.0, 0.0, 0.0 32 | m[2], m[6], m[10], m[14] = 0.0, 0.0, 1.0, 0.0 33 | m[3], m[7], m[11], m[15] = 0.0, 0.0, 0.0, 1.0 34 | } 35 | 36 | // Add adds the values of a 3x3 matrix to this matrix. 37 | func (m *Matrix3) Add(m2 *Matrix3) { 38 | m[0] += m2[0] 39 | m[1] += m2[1] 40 | m[2] += m2[2] 41 | m[3] += m2[3] 42 | m[4] += m2[4] 43 | m[5] += m2[5] 44 | m[6] += m2[6] 45 | m[7] += m2[7] 46 | m[8] += m2[8] 47 | } 48 | 49 | // SetComponents sets the matrix vales based off of the three vectors given. 50 | // Each vector should be arranged as a column in the matrix and should 51 | // then be passed in order. 52 | func (m *Matrix3) SetComponents(v1 *Vector3, v2 *Vector3, v3 *Vector3) { 53 | m[0], m[3], m[6] = v1[0], v2[0], v3[0] 54 | m[1], m[4], m[7] = v1[1], v2[1], v3[1] 55 | m[2], m[5], m[8] = v1[2], v2[2], v3[2] 56 | } 57 | 58 | // SetInertiaTensorCoeffs sets the value of the matrix from inertia tensor values. 59 | func (m *Matrix3) SetInertiaTensorCoeffs(ix, iy, iz, ixy, ixz, iyz Real) { 60 | m[0], m[3], m[6] = ix, -ixy, -ixz 61 | m[1], m[4], m[7] = -ixy, iy, -iyz 62 | m[2], m[5], m[8] = -ixz, -iyz, iz 63 | } 64 | 65 | // SetBlockInertiaTensor sets the value of the matrix as an inertia tensor 66 | // of a rectangular block aligned with the body's coordinate system with the 67 | // given axis half sizes and mass. 68 | func (m *Matrix3) SetBlockInertiaTensor(halfSize *Vector3, mass Real) { 69 | squares := *halfSize 70 | squares.ComponentProduct(halfSize) 71 | m.SetInertiaTensorCoeffs( 72 | 0.3*mass*(squares[1]+squares[2]), 73 | 0.3*mass*(squares[0]+squares[2]), 74 | 0.3*mass*(squares[0]+squares[1]), 75 | 0.0, 0.0, 0.0, 76 | ) 77 | } 78 | 79 | // MulVector3 multiplies a 3x3 matrix by a vector. 80 | func (m *Matrix3) MulVector3(v *Vector3) Vector3 { 81 | return Vector3{ 82 | m[0]*v[0] + m[3]*v[1] + m[6]*v[2], 83 | m[1]*v[0] + m[4]*v[1] + m[7]*v[2], 84 | m[2]*v[0] + m[5]*v[1] + m[8]*v[2], 85 | } 86 | } 87 | 88 | // MulMatrix3 multiplies a 3x3 matrix by another 3x3 matrix. 89 | func (m1 *Matrix3) MulMatrix3(m2 *Matrix3) Matrix3 { 90 | return Matrix3{ 91 | m1[0]*m2[0] + m1[3]*m2[1] + m1[6]*m2[2], 92 | m1[1]*m2[0] + m1[4]*m2[1] + m1[7]*m2[2], 93 | m1[2]*m2[0] + m1[5]*m2[1] + m1[8]*m2[2], 94 | m1[0]*m2[3] + m1[3]*m2[4] + m1[6]*m2[5], 95 | m1[1]*m2[3] + m1[4]*m2[4] + m1[7]*m2[5], 96 | m1[2]*m2[3] + m1[5]*m2[4] + m1[8]*m2[5], 97 | m1[0]*m2[6] + m1[3]*m2[7] + m1[6]*m2[8], 98 | m1[1]*m2[6] + m1[4]*m2[7] + m1[7]*m2[8], 99 | m1[2]*m2[6] + m1[5]*m2[7] + m1[8]*m2[8], 100 | } 101 | } 102 | 103 | // MulWith multiplies a 3x3 matrix by a scalar value. 104 | func (m *Matrix3) MulWith(s Real) { 105 | m[0] *= s 106 | m[1] *= s 107 | m[2] *= s 108 | m[3] *= s 109 | m[4] *= s 110 | m[5] *= s 111 | m[6] *= s 112 | m[7] *= s 113 | m[8] *= s 114 | } 115 | 116 | // Transpose produces the transpose of this matrix. 117 | func (m *Matrix3) Transpose() Matrix3 { 118 | return Matrix3{ 119 | m[0], m[3], m[6], 120 | m[1], m[4], m[7], 121 | m[2], m[5], m[8], 122 | } 123 | } 124 | 125 | // Determinant calculates the determinant of a matrix which is a measure of a square matrix's 126 | // singularity and invertability, among other things. 127 | func (m *Matrix3) Determinant() Real { 128 | return m[0]*m[4]*m[8] + m[3]*m[7]*m[2] + m[6]*m[1]*m[5] - m[6]*m[4]*m[2] - m[3]*m[1]*m[8] - m[0]*m[7]*m[5] 129 | } 130 | 131 | // Inv computes the inverse of a square matrix. An inverse is a square matrix such 132 | // that when multiplied by the original, yields the identity. 133 | func (m *Matrix3) Invert() Matrix3 { 134 | det := m.Determinant() 135 | if RealEqual(det, 0.0) { 136 | return Matrix3{} 137 | } 138 | 139 | retMat := Matrix3{ 140 | m[4]*m[8] - m[5]*m[7], 141 | m[2]*m[7] - m[1]*m[8], 142 | m[1]*m[5] - m[2]*m[4], 143 | m[5]*m[6] - m[3]*m[8], 144 | m[0]*m[8] - m[2]*m[6], 145 | m[2]*m[3] - m[0]*m[5], 146 | m[3]*m[7] - m[4]*m[6], 147 | m[1]*m[6] - m[0]*m[7], 148 | m[0]*m[4] - m[1]*m[3], 149 | } 150 | 151 | retMat.MulWith(1 / det) 152 | return retMat 153 | } 154 | 155 | // TransformTranspose transforms the given vector by the transpose 156 | // of this matrix 157 | func (m *Matrix3) TransformTranspose(v *Vector3) Vector3 { 158 | return Vector3{ 159 | v[0]*m[0] + v[1]*m[1] + v[2]*m[2], 160 | v[0]*m[3] + v[1]*m[4] + v[2]*m[5], 161 | v[0]*m[6] + v[1]*m[7] + v[2]*m[8], 162 | } 163 | } 164 | 165 | // SetAsTransform sets the 3x4 matrix to be a transform matrix based 166 | // on the position and orientation passed in. 167 | func (m *Matrix3x4) SetAsTransform(pos *Vector3, rot *Quat) { 168 | w, x, y, z := rot[0], rot[1], rot[2], rot[3] 169 | 170 | m[0] = 1 - 2*y*y - 2*z*z 171 | m[1] = 2*x*y + 2*w*z 172 | m[2] = 2*x*z - 2*w*y 173 | 174 | m[3] = 2*x*y - 2*w*z 175 | m[4] = 1 - 2*x*x - 2*z*z 176 | m[5] = 2*y*z + 2*w*x 177 | 178 | m[6] = 2*x*z + 2*w*y 179 | m[7] = 2*y*z - 2*w*x 180 | m[8] = 1 - 2*x*x - 2*y*y 181 | 182 | m[9] = pos[0] 183 | m[10] = pos[1] 184 | m[11] = pos[2] 185 | } 186 | 187 | // MulVector3 transforms the given vector by the matrix and returns the result. 188 | func (m *Matrix3x4) MulVector3(v *Vector3) Vector3 { 189 | return Vector3{ 190 | v[0]*m[0] + v[1]*m[3] + v[2]*m[6] + m[9], 191 | v[0]*m[1] + v[1]*m[4] + v[2]*m[7] + m[10], 192 | v[0]*m[2] + v[1]*m[5] + v[2]*m[8] + m[11], 193 | } 194 | } 195 | 196 | // Mul3x4 multiplies a 3x4 matrix by another 3x4 matrix. This operation is meant 197 | // to mimic a 4x4 * 4x4 operation where the last row is {0, 0, 0, 1}. 198 | func (m *Matrix3x4) MulMatrix3x4(o *Matrix3x4) Matrix3x4 { 199 | return Matrix3x4{ 200 | m[0]*o[0] + m[3]*o[1] + m[6]*o[2], // [0] 201 | m[1]*o[0] + m[4]*o[1] + m[7]*o[2], // [1] 202 | m[2]*o[0] + m[5]*o[1] + m[8]*o[2], // [2] 203 | 204 | m[0]*o[3] + m[3]*o[4] + m[6]*o[5], // [3] 205 | m[1]*o[3] + m[4]*o[4] + m[7]*o[5], // [4] 206 | m[2]*o[3] + m[5]*o[4] + m[8]*o[5], // [5] 207 | 208 | m[0]*o[6] + m[3]*o[7] + m[6]*o[8], // [6] 209 | m[1]*o[6] + m[4]*o[7] + m[7]*o[8], // [7] 210 | m[2]*o[6] + m[5]*o[7] + m[8]*o[8], // [8] 211 | 212 | m[0]*o[9] + m[3]*o[10] + m[6]*o[11] + m[9], // [9] 213 | m[1]*o[9] + m[4]*o[10] + m[7]*o[11] + m[10], // [10] 214 | m[2]*o[9] + m[5]*o[10] + m[8]*o[11] + m[11], // [11] 215 | 216 | } 217 | } 218 | 219 | // TransformInverse transforms the vector by the transformational 220 | // inverse of this matrix. 221 | // NOTE: will not work on matrixes with scale or shears. 222 | func (m *Matrix3x4) TransformInverse(v *Vector3) Vector3 { 223 | tmp := *v 224 | tmp[0] -= m[9] 225 | tmp[1] -= m[10] 226 | tmp[2] -= m[11] 227 | 228 | return Vector3{ 229 | tmp[0]*m[0] + tmp[1]*m[1] + tmp[2]*m[2], 230 | tmp[0]*m[3] + tmp[1]*m[4] + tmp[2]*m[5], 231 | tmp[0]*m[6] + tmp[1]*m[7] + tmp[2]*m[8], 232 | } 233 | } 234 | 235 | func (m *Matrix3x4) GetAxis(colNumber int) Vector3 { 236 | var i int 237 | if colNumber > 3 { 238 | i = 3 239 | } else { 240 | i = colNumber 241 | } 242 | return Vector3{ 243 | m[i*3+0], 244 | m[i*3+1], 245 | m[i*3+2], 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /math/matrix_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package math 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestMat3Identity(t *testing.T) { 11 | var m1, m2 Matrix3 12 | m1.SetIdentity() 13 | m2.SetIdentity() 14 | 15 | m3 := m1.MulMatrix3(&m2) 16 | 17 | if !RealEqual(m3[0], 1.0) || !RealEqual(m3[3], 0.0) || !RealEqual(m3[6], 0.0) || 18 | !RealEqual(m3[1], 0.0) || !RealEqual(m3[4], 1.0) || !RealEqual(m3[7], 0.0) || 19 | !RealEqual(m3[2], 0.0) || !RealEqual(m3[5], 0.0) || !RealEqual(m3[8], 1.0) { 20 | t.Errorf("Multiplication of identities does not yield identity:\n\t%v", m3) 21 | } 22 | } 23 | 24 | func TestMat3x4Identity(t *testing.T) { 25 | var m1, m2 Matrix3x4 26 | m1.SetIdentity() 27 | m2.SetIdentity() 28 | 29 | m3 := m1.MulMatrix3x4(&m2) 30 | 31 | if !RealEqual(m3[0], 1.0) || !RealEqual(m3[3], 0.0) || !RealEqual(m3[6], 0.0) || !RealEqual(m3[9], 0.0) || 32 | !RealEqual(m3[1], 0.0) || !RealEqual(m3[4], 1.0) || !RealEqual(m3[7], 0.0) || !RealEqual(m3[10], 0.0) || 33 | !RealEqual(m3[2], 0.0) || !RealEqual(m3[5], 0.0) || !RealEqual(m3[8], 1.0) || !RealEqual(m3[11], 0.0) { 34 | t.Errorf("Multiplication of identities does not yield identity:\n\t%v", m3) 35 | } 36 | } 37 | 38 | func TestMat3Multiplications(t *testing.T) { 39 | var m1 Matrix3 = Matrix3{ 40 | 0.6, 0.2, 0.3, 41 | 0.2, 0.7, 0.5, 42 | 0.3, 0.5, 0.7, 43 | } 44 | 45 | m1Invert := m1.Invert() 46 | m3 := m1.MulMatrix3(&m1Invert) 47 | 48 | if !RealEqual(m3[0], 1.0) || !RealEqual(m3[3], 0.0) || !RealEqual(m3[6], 0.0) || 49 | !RealEqual(m3[1], 0.0) || !RealEqual(m3[4], 1.0) || !RealEqual(m3[7], 0.0) || 50 | !RealEqual(m3[2], 0.0) || !RealEqual(m3[5], 0.0) || !RealEqual(m3[8], 1.0) { 51 | t.Errorf("Multiplication by it's inversion does not yield identity:\n\t%v", m3) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /math/quaternion.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package math 5 | 6 | import "math" 7 | 8 | // QuatFromAxis creates an quaternion from an axis and an angle. 9 | func QuatFromAxis(angle, x, y, z Real) Quat { 10 | s := RealSin(angle / 2.0) 11 | c := RealCos(angle / 2.0) 12 | 13 | result := Quat{c, x * s, y * s, z * s} 14 | result.Normalize() 15 | return result 16 | } 17 | 18 | // AddScaledVector adds the given vector to this quaternion, scaled 19 | // by the given amount. 20 | func (q *Quat) AddScaledVector(v *Vector3, scale Real) { 21 | var temp Quat 22 | temp[0] = 0.0 23 | temp[1] = v[0] * scale 24 | temp[2] = v[1] * scale 25 | temp[3] = v[2] * scale 26 | 27 | temp.Mul(q) 28 | 29 | q[0] += temp[0] * 0.5 30 | q[1] += temp[1] * 0.5 31 | q[2] += temp[2] * 0.5 32 | q[3] += temp[3] * 0.5 33 | } 34 | 35 | // SetIdentity loads the quaternion with its identity. 36 | func (q *Quat) SetIdentity() { 37 | q[0], q[1], q[2], q[3] = 1.0, 0.0, 0.0, 0.0 38 | } 39 | 40 | // Len returns the length of the quaternion. 41 | func (q *Quat) Len() Real { 42 | return RealSqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]) 43 | } 44 | 45 | // Mul multiplies the quaternion by another quaternion. 46 | func (q *Quat) Mul(q2 *Quat) { 47 | var w, x, y, z Real 48 | w = q[0]*q2[0] - q[1]*q2[1] - q[2]*q2[2] - q[3]*q2[3] 49 | x = q[0]*q2[1] + q[1]*q2[0] + q[2]*q2[3] - q[3]*q2[2] 50 | y = q[0]*q2[2] + q[2]*q2[0] + q[3]*q2[1] - q[1]*q2[3] 51 | z = q[0]*q2[3] + q[3]*q2[0] + q[1]*q2[2] - q[2]*q2[1] 52 | q[0], q[1], q[2], q[3] = w, x, y, z 53 | } 54 | 55 | // Rotate rotates a vector by the rotation this quaternion represents. 56 | func (q *Quat) Rotate(v *Vector3) Vector3 { 57 | qvec := Vector3{q[1], q[2], q[3]} 58 | cross := qvec.Cross(v) 59 | 60 | // v + 2q_w * (q_v x v) + 2q_v x (q_v x v) 61 | result := *v 62 | 63 | qvec.MulWith(2.0) 64 | c2 := qvec.Cross(&cross) 65 | result.Add(&c2) 66 | 67 | cross.MulWith(2.0 * q[0]) 68 | result.Add(&cross) 69 | return result 70 | } 71 | 72 | // Conjugated returns the conjugate of a quaternion. Equivalent to Quat{w,-x,-y,-z}. 73 | func (q *Quat) Conjugated() Quat { 74 | return Quat{q[0], -q[1], -q[2], -q[3]} 75 | } 76 | 77 | // Normalize normalizes the quaternion. 78 | func (q *Quat) Normalize() { 79 | length := q.Len() 80 | 81 | if RealEqual(1.0, length) { 82 | return 83 | } 84 | 85 | if length == 0.0 { 86 | q.SetIdentity() 87 | return 88 | } 89 | 90 | if length == InfPos { 91 | length = MaxValue 92 | } 93 | 94 | invLength := 1.0 / length 95 | q[0] *= invLength 96 | q[1] *= invLength 97 | q[2] *= invLength 98 | q[3] *= invLength 99 | } 100 | 101 | // LookAt sets the quaternion to the orientation needed to look at a 'center' from 102 | // the 'eye' position with 'up' as a reference vector for the up direction. 103 | // Note: this was modified from the go-gl/mathgl library. 104 | func (q *Quat) LookAt(eye, center, up *Vector3) { 105 | direction := center 106 | direction.Sub(eye) 107 | direction.Normalize() 108 | 109 | // Find the rotation between the front of the object (that we assume towards Z-, 110 | // but this depends on your model) and the desired direction 111 | rotDir := QuatBetweenVectors(&Vector3{0, 0, -1}, direction) 112 | 113 | // Recompute up so that it's perpendicular to the direction 114 | // You can skip that part if you really want to force up 115 | //right := direction.Cross(up) 116 | //up = right.Cross(direction) 117 | 118 | // Because of the 1rst rotation, the up is probably completely screwed up. 119 | // Find the rotation between the "up" of the rotated object, and the desired up 120 | upCur := rotDir.Rotate(&Vector3{0, 1, 0}) 121 | rotTarget := QuatBetweenVectors(&upCur, up) 122 | 123 | rotTarget.Mul(&rotDir) // remember, in reverse order. 124 | rotTarget.Inverse() // camera rotation should be inversed! 125 | 126 | q[0] = rotTarget[0] 127 | q[1] = rotTarget[1] 128 | q[2] = rotTarget[2] 129 | q[3] = rotTarget[3] 130 | } 131 | 132 | // QuatBetweenVectors calculates the rotation between two vectors. 133 | // Note: this was modified from the go-gl/mathgl library. 134 | func QuatBetweenVectors(s, d *Vector3) Quat { 135 | start := *s 136 | dest := *d 137 | start.Normalize() 138 | dest.Normalize() 139 | 140 | cosTheta := start.Dot(&dest) 141 | if cosTheta < -1.0+Epsilon { 142 | // special case when vectors in opposite directions: 143 | // there is no "ideal" rotation axis 144 | // So guess one; any will do as long as it's perpendicular to start 145 | posX := Vector3{1.0, 0.0, 0.0} 146 | axis := posX.Cross(&start) 147 | if axis.Dot(&axis) < Epsilon { 148 | // bad luck, they were parallel, try again! 149 | posY := Vector3{0.0, 1.0, 0.0} 150 | axis = posY.Cross(&start) 151 | } 152 | 153 | axis.Normalize() 154 | return QuatFromAxis(math.Pi, axis[0], axis[1], axis[2]) 155 | } 156 | 157 | axis := start.Cross(&dest) 158 | ang := RealSqrt((1.0 + cosTheta) * 2.0) 159 | axis.MulWith(1.0 / ang) 160 | 161 | return Quat{ 162 | ang * 0.5, 163 | axis[0], axis[1], axis[2], 164 | } 165 | } 166 | 167 | // Inverse calculates the inverse of a quaternion. The inverse is equivalent 168 | // to the conjugate divided by the square of the length. 169 | // 170 | // This method computes the square norm by directly adding the sum 171 | // of the squares of all terms instead of actually squaring q1.Len(), 172 | // both for performance and percision. 173 | func (q *Quat) Inverse() { 174 | c := q.Conjugated() 175 | c.Scale(1.0 / q.Dot(q)) 176 | q[0] = c[0] 177 | q[1] = c[1] 178 | q[2] = c[2] 179 | q[3] = c[3] 180 | } 181 | 182 | // Scale scales every element of the quaternion by some constant factor. 183 | func (q *Quat) Scale(c Real) { 184 | q[0] *= c 185 | q[1] *= c 186 | q[2] *= c 187 | q[3] *= c 188 | } 189 | 190 | // Dot calculates the dot product between two quaternions, equivalent to if this was a Vector4 191 | func (q *Quat) Dot(q2 *Quat) Real { 192 | return q[0]*q2[0] + q[1]*q2[1] + q[2]*q2[2] + q[3]*q2[3] 193 | } 194 | -------------------------------------------------------------------------------- /math/quaternion_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package math 5 | 6 | import ( 7 | "math" 8 | "testing" 9 | ) 10 | 11 | func TestQuatMulIdentity(t *testing.T) { 12 | var i1, i2 Quat 13 | i1.SetIdentity() 14 | i2.SetIdentity() 15 | 16 | i1.Mul(&i2) 17 | 18 | if !RealEqual(i1[0], 1.0) || !RealEqual(i1[1], 0.0) || !RealEqual(i1[2], 0.0) || !RealEqual(i1[3], 0.0) { 19 | t.Errorf("Multiplication of identities does not yield identity") 20 | } 21 | } 22 | 23 | func TestQuatLen(t *testing.T) { 24 | q1 := Quat{0.0, 1.0, 0.0, 0.0} 25 | len1 := q1.Len() 26 | if !RealEqual(len1, 1.0) { 27 | t.Errorf("Quaternion calculation of length didn't yield the correct answer: %v", len1) 28 | } 29 | 30 | q2 := Quat{0.0, 0.0000000000001, 0.0, 0.0} 31 | len2 := q2.Len() 32 | if !RealEqual(len2, 1e-13) { 33 | t.Errorf("Quaternion calculation of length didn't yield the correct answer: %v", len2) 34 | } 35 | 36 | q3 := Quat{0.0, MaxValue, 1.0, 0.0} 37 | len3 := q3.Len() 38 | if !RealEqual(len3, InfPos) { 39 | t.Errorf("Quaternion calculation of length didn't yield the correct answer: %v", len3) 40 | } 41 | 42 | q4 := Quat{4.0, 1.0, 2.0, 3.0} 43 | len4 := q4.Len() 44 | if !RealEqual(len4, Real(math.Sqrt(1*1+2*2+3*3+4*4))) { 45 | t.Errorf("Quaternion calculation of length didn't yield the correct answer: %v", len4) 46 | } 47 | } 48 | 49 | func TestQuatNormalize(t *testing.T) { 50 | q1 := Quat{0.0, 0.0, 0.0, 0.0} 51 | q1.Normalize() 52 | if !RealEqual(q1[0], 1.0) || !RealEqual(q1[1], 0.0) || !RealEqual(q1[2], 0.0) || !RealEqual(q1[3], 0.0) { 53 | t.Errorf("Quaternion normalization didn't yield the correct quaternion: %v", q1) 54 | } 55 | 56 | q1 = Quat{0.0, 1.0, 0.0, 0.0} 57 | q1.Normalize() 58 | if !RealEqual(q1[0], 0.0) || !RealEqual(q1[1], 1.0) || !RealEqual(q1[2], 0.0) || !RealEqual(q1[3], 0.0) { 59 | t.Errorf("Quaternion normalization didn't yield the correct quaternion: %v", q1) 60 | } 61 | 62 | q1 = Quat{0.0, 0.0000000000001, 0.0, 0.0} 63 | q1.Normalize() 64 | if !RealEqual(q1[0], 0.0) || !RealEqual(q1[1], 1.0) || !RealEqual(q1[2], 0.0) || !RealEqual(q1[3], 0.0) { 65 | t.Errorf("Quaternion normalization didn't yield the correct quaternion: %v", q1) 66 | } 67 | 68 | q1 = Quat{0.0, MaxValue, 1.0, 0.0} 69 | q1.Normalize() 70 | if !RealEqual(q1[0], 0.0) || !RealEqual(q1[1], 1.0) || !RealEqual(q1[2], 0.0) || !RealEqual(q1[3], 0.0) { 71 | t.Errorf("Quaternion normalization didn't yield the correct quaternion: %v", q1) 72 | } 73 | } 74 | 75 | func TestQuatMul(t *testing.T) { 76 | q1 := Quat{1.0, 0.5, -3.0, 4.0} 77 | q2 := Quat{6.0, 2.0, 1.0, -9.0} 78 | 79 | q1.Mul(&q2) 80 | 81 | if !RealEqual(q1[0], 44.0) || !RealEqual(q1[1], 28.0) || !RealEqual(q1[2], -4.5) || !RealEqual(q1[3], 21.5) { 82 | t.Errorf("Quaternion multiplication didn't yield the correct quaternion: %v", q1) 83 | } 84 | } 85 | 86 | func TestQuatRotate(t *testing.T) { 87 | q := Quat{1.0, 0.0, 1.0, 0.0} 88 | v := Vector3{1.0, 0.0, 0.0} 89 | q.Normalize() 90 | result := q.Rotate(&v) 91 | if !RealEqual(result[0], 0.0) || !RealEqual(result[1], 0.0) || !RealEqual(result[2], -1.0) { 92 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 93 | } 94 | 95 | // y-axis rotation 96 | q = QuatFromAxis(0.0, 0.0, 1.0, 0.0) 97 | v = Vector3{1.0, 0.0, 0.0} 98 | result = q.Rotate(&v) 99 | if !RealEqual(result[0], 1.0) || !RealEqual(result[1], 0.0) || !RealEqual(result[2], 0.0) { 100 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 101 | } 102 | 103 | q = QuatFromAxis(DegToRad(90), 0.0, 1.0, 0.0) 104 | v = Vector3{1.0, 0.0, 0.0} 105 | result = q.Rotate(&v) 106 | if !RealEqual(result[0], 0.0) || !RealEqual(result[1], 0.0) || !RealEqual(result[2], -1.0) { 107 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 108 | } 109 | 110 | q = QuatFromAxis(DegToRad(180), 0.0, 1.0, 0.0) 111 | v = Vector3{1.0, 0.0, 0.0} 112 | result = q.Rotate(&v) 113 | if !RealEqual(result[0], -1.0) || !RealEqual(result[1], 0.0) || !RealEqual(result[2], 0.0) { 114 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 115 | } 116 | 117 | q = QuatFromAxis(DegToRad(270), 0.0, 1.0, 0.0) 118 | v = Vector3{0.0, 0.0, 1.0} 119 | result = q.Rotate(&v) 120 | if !RealEqual(result[0], -1.0) || !RealEqual(result[1], 0.0) || !RealEqual(result[2], 0.0) { 121 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 122 | } 123 | 124 | // x-axis rotation 125 | q = QuatFromAxis(0.0, 0.0, 0.0, 1.0) 126 | v = Vector3{0.0, 1.0, 0.0} 127 | result = q.Rotate(&v) 128 | if !RealEqual(result[0], 0.0) || !RealEqual(result[1], 1.0) || !RealEqual(result[2], 0.0) { 129 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 130 | } 131 | 132 | q = QuatFromAxis(DegToRad(90), 1.0, 0.0, 0.0) 133 | v = Vector3{0.0, 1.0, 0.0} 134 | result = q.Rotate(&v) 135 | if !RealEqual(result[0], 0.0) || !RealEqual(result[1], 0.0) || !RealEqual(result[2], 1.0) { 136 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 137 | } 138 | 139 | q = QuatFromAxis(DegToRad(180), 1.0, 0.0, 0.0) 140 | v = Vector3{0.0, 1.0, 0.0} 141 | result = q.Rotate(&v) 142 | if !RealEqual(result[0], 0.0) || !RealEqual(result[1], -1.0) || !RealEqual(result[2], 0.0) { 143 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 144 | } 145 | 146 | q = QuatFromAxis(DegToRad(270), 1.0, 0.0, 0.0) 147 | v = Vector3{0.0, 1.0, 0.0} 148 | result = q.Rotate(&v) 149 | if !RealEqual(result[0], 0.0) || !RealEqual(result[1], 0.0) || !RealEqual(result[2], -1.0) { 150 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 151 | } 152 | 153 | // z-axis rotation 154 | q = QuatFromAxis(0.0, 0.0, 0.0, 1.0) 155 | v = Vector3{0.0, 1.0, 0.0} 156 | result = q.Rotate(&v) 157 | if !RealEqual(result[0], 0.0) || !RealEqual(result[1], 1.0) || !RealEqual(result[2], 0.0) { 158 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 159 | } 160 | 161 | q = QuatFromAxis(DegToRad(90), 0.0, 0.0, 1.0) 162 | v = Vector3{0.0, 1.0, 0.0} 163 | result = q.Rotate(&v) 164 | if !RealEqual(result[0], -1.0) || !RealEqual(result[1], 0.0) || !RealEqual(result[2], 0.0) { 165 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 166 | } 167 | 168 | q = QuatFromAxis(DegToRad(180), 0.0, 0.0, 1.0) 169 | v = Vector3{0.0, 1.0, 0.0} 170 | result = q.Rotate(&v) 171 | if !RealEqual(result[0], 0.0) || !RealEqual(result[1], -1.0) || !RealEqual(result[2], 0.0) { 172 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 173 | } 174 | 175 | q = QuatFromAxis(DegToRad(270), 0.0, 0.0, 1.0) 176 | v = Vector3{0.0, 1.0, 0.0} 177 | result = q.Rotate(&v) 178 | if !RealEqual(result[0], 1.0) || !RealEqual(result[1], 0.0) || !RealEqual(result[2], 0.0) { 179 | t.Errorf("Quaternion rotation didn't yield the correct vector:\n\t(q=%v:v%v)%v", q, v, result) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /math/vector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package math 5 | 6 | // Add adds a vector to another vector. 7 | func (v *Vector3) Add(v2 *Vector3) { 8 | v[0] += v2[0] 9 | v[1] += v2[1] 10 | v[2] += v2[2] 11 | } 12 | 13 | // Add adds a vector, scaled by a Real, to another vector. 14 | func (v *Vector3) AddScaled(v2 *Vector3, scale Real) { 15 | v[0] += v2[0] * scale 16 | v[1] += v2[1] * scale 17 | v[2] += v2[2] * scale 18 | } 19 | 20 | // Clear sets the vector to {0.0, 0.0, 0.0}. 21 | func (v *Vector3) Clear() { 22 | v[0], v[1], v[2] = 0.0, 0.0, 0.0 23 | } 24 | 25 | // ComponentProduct performs a component-wise product with another vector. 26 | func (v *Vector3) ComponentProduct(v2 *Vector3) { 27 | v[0] *= v2[0] 28 | v[1] *= v2[1] 29 | v[2] *= v2[2] 30 | } 31 | 32 | // Cross returns the cross product of this vector with another. 33 | func (v *Vector3) Cross(v2 *Vector3) Vector3 { 34 | return Vector3{ 35 | v[1]*v2[2] - v[2]*v2[1], 36 | v[2]*v2[0] - v[0]*v2[2], 37 | v[0]*v2[1] - v[1]*v2[0], 38 | } 39 | } 40 | 41 | // Dot returns the dot product of this vector with another. 42 | func (v *Vector3) Dot(v2 *Vector3) Real { 43 | return v[0]*v2[0] + v[1]*v2[1] + v[2]*v2[2] 44 | } 45 | 46 | // Magnitude returns the magnitude of the vector. 47 | func (v *Vector3) Magnitude() Real { 48 | return RealSqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]) 49 | } 50 | 51 | // SquareMagnitude returns the magitude of the vector, squared. 52 | func (v *Vector3) SquareMagnitude() Real { 53 | return v[0]*v[0] + v[1]*v[1] + v[2]*v[2] 54 | } 55 | 56 | // MulWith multiplies a vector by a Real number. 57 | func (v *Vector3) MulWith(r Real) { 58 | v[0] *= r 59 | v[1] *= r 60 | v[2] *= r 61 | } 62 | 63 | // Normalize sets the vector the normalized value. 64 | func (v *Vector3) Normalize() { 65 | m := v.Magnitude() 66 | if !RealEqual(m, 0.0) { 67 | l := 1.0 / m 68 | v[0] *= l 69 | v[1] *= l 70 | v[2] *= l 71 | } 72 | } 73 | 74 | // Set sets the vector equal to the values of the second vector. 75 | func (v *Vector3) Set(v2 *Vector3) { 76 | v[0] = v2[0] 77 | v[1] = v2[1] 78 | v[2] = v2[2] 79 | } 80 | 81 | // Sub subtracts a second vector from this vector. 82 | func (v *Vector3) Sub(v2 *Vector3) { 83 | v[0] -= v2[0] 84 | v[1] -= v2[1] 85 | v[2] -= v2[2] 86 | } 87 | 88 | // MulWith multiplies a vector by a Real number. 89 | func (v *Vector4) MulWith(r Real) { 90 | v[0] *= r 91 | v[1] *= r 92 | v[2] *= r 93 | v[3] *= r 94 | } 95 | -------------------------------------------------------------------------------- /math/vector_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package math 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | /* ================================ VECTOR 3 ================================================ */ 11 | 12 | func TestVector3Add(t *testing.T) { 13 | v1 := Vector3{1.0, 2.5, 3.75} 14 | v2 := Vector3{0.0, 1.0, 7.0} 15 | 16 | v1.Add(&v2) 17 | 18 | if !RealEqual(v2[0], 0.0) || !RealEqual(v2[1], 1.0) || !RealEqual(v2[2], 7.0) { 19 | t.Errorf("Add modified an original vector while calculating addition: %v", v2) 20 | } 21 | if !RealEqual(v1[0], 1.0) || !RealEqual(v1[1], 3.5) || !RealEqual(v1[2], 10.75) { 22 | t.Errorf("Vector Add not adding properly: %v", v1) 23 | } 24 | 25 | v2.Add(&v1) 26 | 27 | if !RealEqual(v1[0], 1.0) || !RealEqual(v1[1], 3.5) || !RealEqual(v1[2], 10.75) { 28 | t.Errorf("Add modified an original vector while calculating addition: %v", v2) 29 | } 30 | if !RealEqual(v2[0], 1.0) || !RealEqual(v2[1], 4.5) || !RealEqual(v2[2], 17.75) { 31 | t.Errorf("vVector Add is somehow not commutative: %v", v2) 32 | } 33 | } 34 | 35 | func TestVector3AddScaled(t *testing.T) { 36 | v1 := Vector3{1.0, 2.5, 3.75} 37 | v2 := Vector3{0.0, 1.0, 7.0} 38 | var scale Real = 3.0 39 | 40 | v1.AddScaled(&v2, scale) 41 | 42 | if !RealEqual(v2[0], 0.0) || !RealEqual(v2[1], 1.0) || !RealEqual(v2[2], 7.0) { 43 | t.Errorf("AddScaled modified an original vector while calculating AddScaled: %v", v2) 44 | } 45 | if !RealEqual(v1[0], 1.0) || !RealEqual(v1[1], 5.5) || !RealEqual(v1[2], 24.75) { 46 | t.Errorf("AddScaled not adding a scaled vector properly: %v", v1) 47 | } 48 | } 49 | 50 | func TestVector3Clear(t *testing.T) { 51 | v1 := Vector3{1.0, 2.5, 3.75} 52 | 53 | v1.Clear() 54 | 55 | if !RealEqual(v1[0], 0.0) || !RealEqual(v1[1], 0.0) || !RealEqual(v1[2], 0.0) { 56 | t.Errorf("Clear isn't clearing a vector properly: %v", v1) 57 | } 58 | } 59 | 60 | func TestVector3ComponentProduct(t *testing.T) { 61 | v1 := Vector3{1.0, 2.5, 3.5} 62 | v2 := Vector3{0.0, 1.0, 7.0} 63 | 64 | v1.ComponentProduct(&v2) 65 | 66 | if !RealEqual(v2[0], 0.0) || !RealEqual(v2[1], 1.0) || !RealEqual(v2[2], 7.0) { 67 | t.Errorf("ComponentProduct modified an original vector while calculating a cross product: %v", v2) 68 | } 69 | if !RealEqual(v1[0], 0.0) || !RealEqual(v1[1], 2.5) || !RealEqual(v1[2], 24.5) { 70 | t.Errorf("ComponentProduct isn't multiplying a vector properly: %v", v1) 71 | } 72 | } 73 | 74 | func TestVector3Cross(t *testing.T) { 75 | v1 := Vector3{1.0, 2.0, 3.0} 76 | v2 := Vector3{10.0, 11.0, 12.0} 77 | 78 | v3 := v1.Cross(&v2) 79 | 80 | if !RealEqual(v1[0], 1.0) || !RealEqual(v1[1], 2.0) || !RealEqual(v1[2], 3.0) { 81 | t.Errorf("Cross modified an original vector while calculating a cross product: %v", v1) 82 | } 83 | if !RealEqual(v2[0], 10.0) || !RealEqual(v2[1], 11.0) || !RealEqual(v2[2], 12.0) { 84 | t.Errorf("Cross modified an original vector while calculating a cross product: %v", v1) 85 | } 86 | if !RealEqual(v3[0], -9.0) || !RealEqual(v3[1], 18.0) || !RealEqual(v3[2], -9.0) { 87 | t.Errorf("Cross isn't calculating a cross product properly: %v", v3) 88 | } 89 | } 90 | 91 | func TestVector3Dot(t *testing.T) { 92 | v1 := Vector3{-1.0, -5.0, -7.0} 93 | v2 := Vector3{10.0, 20.0, 30.0} 94 | 95 | dot := v1.Dot(&v2) 96 | 97 | if !RealEqual(v1[0], -1.0) || !RealEqual(v1[1], -5.0) || !RealEqual(v1[2], -7.0) { 98 | t.Errorf("Dot modified an original vector while calculating a dot product: %v", v1) 99 | } 100 | if !RealEqual(v2[0], 10.0) || !RealEqual(v2[1], 20.0) || !RealEqual(v2[2], 30.0) { 101 | t.Errorf("Dot modified an original vector while calculating a dot product: %v", v1) 102 | } 103 | if !RealEqual(dot, -320.0) { 104 | t.Errorf("Dot isn't calculating a dot product properly: %v", dot) 105 | } 106 | } 107 | 108 | func TestVector3Magnitude(t *testing.T) { 109 | v1 := Vector3{2.0, -5.0, 4.0} 110 | 111 | mag := v1.Magnitude() 112 | 113 | if !RealEqual(v1[0], 2.0) || !RealEqual(v1[1], -5.0) || !RealEqual(v1[2], 4.0) { 114 | t.Errorf("Magnitude modified an original vector while calculating a magnitude: %v", v1) 115 | } 116 | if !RealEqual(mag, 6.708203932499369) { 117 | t.Errorf("Magnitude isn't calculating a magnitude properly: %v", mag) 118 | } 119 | } 120 | 121 | func TestVector3SquareMagnitude(t *testing.T) { 122 | v1 := Vector3{2.0, -5.0, 4.0} 123 | 124 | sqmag := v1.SquareMagnitude() 125 | 126 | if !RealEqual(v1[0], 2.0) || !RealEqual(v1[1], -5.0) || !RealEqual(v1[2], 4.0) { 127 | t.Errorf("Magnitude modified an original vector while calculating a magnitude: %v", v1) 128 | } 129 | if !RealEqual(sqmag, 4.0+25+16) { 130 | t.Errorf("Magnitude isn't calculating a squared magnitude properly: %v", sqmag) 131 | } 132 | } 133 | 134 | func TestVector3MulWith(t *testing.T) { 135 | v1 := Vector3{1.0, 2.5, 3.5} 136 | 137 | v1.MulWith(10.0) 138 | 139 | if !RealEqual(v1[0], 10.0) || !RealEqual(v1[1], 25.0) || !RealEqual(v1[2], 35.0) { 140 | t.Errorf("MulWith isn't multiplying a vector properly: %v", v1) 141 | } 142 | } 143 | 144 | func TestVector3Normalize(t *testing.T) { 145 | v1 := Vector3{1.0, 2.5, 3.5} 146 | v1.Normalize() 147 | if !RealEqual(v1.Magnitude(), 1.0) { 148 | t.Errorf("Normalize isn't normalizing a vector properly: %v (len:%v)", v1, v1.Magnitude()) 149 | } 150 | } 151 | 152 | func TestVector3Set(t *testing.T) { 153 | v1 := Vector3{-1.0, -5.0, -7.0} 154 | v2 := Vector3{10.0, 20.0, 30.0} 155 | 156 | v1.Set(&v2) 157 | 158 | if !RealEqual(v2[0], 10.0) || !RealEqual(v2[1], 20.0) || !RealEqual(v2[2], 30.0) { 159 | t.Errorf("Set modified an original vector while setting another vector: %v", v2) 160 | } 161 | if !RealEqual(v1[0], 10.0) || !RealEqual(v1[1], 20.0) || !RealEqual(v1[2], 30.0) { 162 | t.Errorf("Set did not set vector members properly: %v", v1) 163 | } 164 | } 165 | 166 | func TestVector3Sub(t *testing.T) { 167 | v1 := Vector3{-1.0, -5.0, -7.0} 168 | v2 := Vector3{10.0, 20.0, 30.0} 169 | 170 | v1.Sub(&v2) 171 | 172 | if !RealEqual(v2[0], 10.0) || !RealEqual(v2[1], 20.0) || !RealEqual(v2[2], 30.0) { 173 | t.Errorf("Sub modified an original vector while subtracting another vector: %v", v2) 174 | } 175 | if !RealEqual(v1[0], -11.0) || !RealEqual(v1[1], -25.0) || !RealEqual(v1[2], -37.0) { 176 | t.Errorf("Sub did not subtract the vector properly: %v", v1) 177 | } 178 | } 179 | 180 | /* ================================ VECTOR 4 ================================================ */ 181 | 182 | func TestVector4MulWith(t *testing.T) { 183 | v1 := Vector4{1.0, 2.5, 3.5, 98.7} 184 | 185 | v1.MulWith(10.0) 186 | 187 | if !RealEqual(v1[0], 10.0) || !RealEqual(v1[1], 25.0) || !RealEqual(v1[2], 35.0) || !RealEqual(v1[3], 987.0) { 188 | t.Errorf("MulWith isn't multiplying a vector properly: %v", v1) 189 | } 190 | } 191 | 192 | /* ================================ HOW DOES GO WORK ============================================= */ 193 | 194 | func TestVectorGoCopies(t *testing.T) { 195 | var v1 Vector3 = Vector3{1.0, 2.5, 3.5} 196 | var vArray [2]Vector3 197 | 198 | // This test illustrates that Go makes a copy of the float array here and that subsequent 199 | // operations on the new copy do not modify the original. 200 | vArray[0] = v1 201 | vArray[0].MulWith(2.0) 202 | 203 | if !RealEqual(vArray[0][0], 2.0) || !RealEqual(vArray[0][1], 5.0) || !RealEqual(vArray[0][2], 7.0) { 204 | t.Errorf("MulWith isn't multiplying a vector properly: %v", v1) 205 | } 206 | 207 | if !RealEqual(v1[0], 1.0) || !RealEqual(v1[1], 2.5) || !RealEqual(v1[2], 3.5) { 208 | t.Errorf("The initial vector was modified in the multiplication after a copy: %v", v1) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /rigidbody.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package cubez 5 | 6 | import ( 7 | m "github.com/tbogdala/cubez/math" 8 | "math" 9 | ) 10 | 11 | const ( 12 | defaultLinearDamping = 0.95 13 | defaultAngularDamping = 0.8 14 | sleepEpsilon = 0.3 15 | ) 16 | 17 | var ( 18 | defaultAcceleration = m.Vector3{0.0, -9.78, 0.0} 19 | ) 20 | 21 | // RigidBody is the main data structure represending an object that can 22 | // cause collisions and move around in the physics simulation. 23 | type RigidBody struct { 24 | // LinearDamping holds the amount of damping applied to the linear motion 25 | // of the RigidBody. This is required to remove energy that might get 26 | // added due to the numerical instability of floating point operations. 27 | LinearDamping m.Real 28 | 29 | // AngularDamping holds the amount of damping applied to angular modtion 30 | // of the RigidBody. Damping is required to remove energy added through 31 | // numerical instability in the integrator. 32 | AngularDamping m.Real 33 | 34 | // Position is the position of the RigidBody in World Space. 35 | Position m.Vector3 36 | 37 | // Orientation is the angular orientation of the RigidBody. 38 | Orientation m.Quat 39 | 40 | // Velocity is the linear velocity of the RigidBody in World Space. 41 | Velocity m.Vector3 42 | 43 | // Acceleration is the acceleration of the RigidBody and can be 44 | // used to set acceleration due to gravity or any other constant 45 | // acceleration desired. 46 | Acceleration m.Vector3 47 | 48 | // Rotation holds the angular velocity, or rotation, of the rigid body in World Space. 49 | Rotation m.Vector3 50 | 51 | // InverseInertiaTensor holds the inverse of the rigid body's inertia tensor. 52 | // The inertia tensor provided must not be degenerate (that would mean the 53 | // body had zero inertia for spinning along one axis). As long as the tensor 54 | // is finite, it will be invertible. The inverse tensor is used for similar 55 | // reasons to the use of the inverse mass. 56 | // NOTE: this is given in Body Space. 57 | InverseInertiaTensor m.Matrix3 58 | 59 | // IsAwake indicates if the RigidBody is awake and should be updated 60 | // upon integration. 61 | // Defaults to true. 62 | IsAwake bool 63 | 64 | // CanSleep indicates if the RigidBody is allowed to 'sleep' or 65 | // if it should always be awake. 66 | // Defaults to true. 67 | CanSleep bool 68 | 69 | // inverseInertiaTensorWorld holdes the inverse inertia tensor of the 70 | // body in World Space. 71 | inverseInertiaTensorWorld m.Matrix3 72 | 73 | // inverseMass holds the inverse of the mass of the RigidBody which 74 | // is used much more often in calculations than just the mass. 75 | inverseMass m.Real 76 | 77 | // mass is the stored mass of the object and is used to calculate 78 | // inverseMass. 79 | // NOTE: This variable should not be changed directly unless 80 | // inverseMass is also changed. 81 | mass m.Real 82 | 83 | // transform holds a transofm matrix for converting Body Space into World Space. 84 | transform m.Matrix3x4 85 | 86 | // forceAccum stores the accumulated force of the RigidBody to be applied 87 | // at the next integration. 88 | forceAccum m.Vector3 89 | 90 | // torqueAccum stores the accumulated torque of the RigidBody to be applied 91 | // at the next integration. 92 | torqueAccum m.Vector3 93 | 94 | // lastFrameAccelleration holds the linear accelleration of the RigidBody for 95 | // the previous frame 96 | lastFrameAccelleration m.Vector3 97 | 98 | // motion holds the amount of motion of the body and is a recently weighted 99 | // mean that can be used to put a body to sleep. 100 | motion m.Real 101 | } 102 | 103 | // NewRigidBody creates a new RigidBody object and returns it. 104 | func NewRigidBody() *RigidBody { 105 | body := new(RigidBody) 106 | body.Orientation.SetIdentity() 107 | body.LinearDamping = defaultLinearDamping 108 | body.AngularDamping = defaultLinearDamping 109 | body.Acceleration = defaultAcceleration 110 | body.inverseInertiaTensorWorld.SetIdentity() 111 | body.CanSleep = true 112 | body.SetAwake(true) 113 | return body 114 | } 115 | 116 | // Clone makes a new RigidBody object with the current data of the RigidBody this is called on. 117 | func (body *RigidBody) Clone() *RigidBody { 118 | newBody := NewRigidBody() 119 | *newBody = *body 120 | return newBody 121 | } 122 | 123 | // SetMass sets the mass of the RigidBody object. 124 | func (body *RigidBody) SetMass(mass m.Real) { 125 | body.mass = mass 126 | body.inverseMass = 1.0 / mass 127 | } 128 | 129 | // SetInfiniteMass sets the mass of the RigidBody object to be 'infinite' ... which 130 | // actually just sets the inverse mass to 0.0. 131 | func (body *RigidBody) SetInfiniteMass() { 132 | body.mass = 0.0 133 | body.inverseMass = 0.0 134 | } 135 | 136 | // HasFiniteMass returns true if the RigidBody has a finite mass (not infinite). 137 | func (body *RigidBody) HasFiniteMass() bool { 138 | if body.inverseMass > 0.0 { 139 | return true 140 | } 141 | return false 142 | } 143 | 144 | // GetMass gets the mass of the RigidBody object. 145 | func (body *RigidBody) GetMass() m.Real { 146 | if body.inverseMass == 0.0 { 147 | return m.MaxValue 148 | } 149 | return body.mass 150 | } 151 | 152 | // GetInverseMass gets the inverse mass of the RigidBody object. 153 | func (body *RigidBody) GetInverseMass() m.Real { 154 | return body.inverseMass 155 | } 156 | 157 | // GetTransform returns a copy of the RigidBody's calculated transform matrix 158 | func (body *RigidBody) GetTransform() m.Matrix3x4 { 159 | return body.transform 160 | } 161 | 162 | // GetLastFrameAccelleration returns a copy of the RigidBody's linear accelleration 163 | // for the last frame. 164 | func (body *RigidBody) GetLastFrameAccelleration() m.Vector3 { 165 | return body.lastFrameAccelleration 166 | } 167 | 168 | // GetInverseInertiaTensorWorld returns a copy of the RigidBody's inverse 169 | // inertia tensor in World Space. 170 | func (body *RigidBody) GetInverseInertiaTensorWorld() m.Matrix3 { 171 | return body.inverseInertiaTensorWorld 172 | } 173 | 174 | // SetInertiaTensor sets the InverseInertiaTensor member of the RigidBody 175 | // by calculating the inverse of the matrix supplied. 176 | func (body *RigidBody) SetInertiaTensor(m *m.Matrix3) { 177 | body.InverseInertiaTensor = m.Invert() 178 | } 179 | 180 | // SetAwake sets the IsAwake property of the RigidBody. 181 | // NOTE: this function doesn't respect CanSleep. 182 | func (body *RigidBody) SetAwake(awake bool) { 183 | if awake { 184 | body.IsAwake = true 185 | // add some motion to avoid it falling asleep immediately 186 | body.motion = sleepEpsilon * 2.0 187 | } else { 188 | body.IsAwake = false 189 | body.Velocity.Clear() 190 | body.Rotation.Clear() 191 | } 192 | } 193 | 194 | // AddVelocity adds the vector to the RigidBody's Velocity property. 195 | func (body *RigidBody) AddVelocity(v *m.Vector3) { 196 | body.Velocity.Add(v) 197 | } 198 | 199 | // AddRotation adds the vector to the RigidBody's Rotation property. 200 | func (body *RigidBody) AddRotation(v *m.Vector3) { 201 | body.Rotation.Add(v) 202 | } 203 | 204 | // ClearAccumulators resets all of the stored linear and torque forces 205 | // stored in the body. 206 | func (body *RigidBody) ClearAccumulators() { 207 | body.forceAccum[0], body.forceAccum[1], body.forceAccum[2] = 0.0, 0.0, 0.0 208 | body.torqueAccum[0], body.torqueAccum[1], body.torqueAccum[2] = 0.0, 0.0, 0.0 209 | } 210 | 211 | // Integrate takes all of the forces accumulated in the RigidBody and 212 | // change the Position and Orientation of the object. 213 | func (body *RigidBody) Integrate(duration m.Real) { 214 | if body.IsAwake == false { 215 | return 216 | } 217 | 218 | // calculate linear acceleration from force inputs. 219 | body.lastFrameAccelleration = body.Acceleration 220 | body.lastFrameAccelleration.AddScaled(&body.forceAccum, body.inverseMass) 221 | 222 | // calculate angular acceleration from torque inputs 223 | angularAcceleration := body.inverseInertiaTensorWorld.MulVector3(&body.torqueAccum) 224 | 225 | // adjust velocities 226 | // update linear velocity from both acceleration and impulse 227 | body.Velocity.AddScaled(&body.lastFrameAccelleration, duration) 228 | 229 | // update angular velocity from both acceleration and impulse 230 | body.Rotation.AddScaled(&angularAcceleration, duration) 231 | 232 | // impose drag 233 | body.Velocity.MulWith(m.Real(math.Pow(float64(body.LinearDamping), float64(duration)))) 234 | body.Rotation.MulWith(m.Real(math.Pow(float64(body.AngularDamping), float64(duration)))) 235 | 236 | // adjust positions 237 | // update linear positions 238 | body.Position.AddScaled(&body.Velocity, duration) 239 | 240 | //update angular position 241 | body.Orientation.AddScaledVector(&body.Rotation, duration) 242 | 243 | // normalize the orientation and update the matrixes with the new position and orientation 244 | body.CalculateDerivedData() 245 | body.ClearAccumulators() 246 | 247 | // update the kinetic energy store and possibly put the body to sleep 248 | if body.CanSleep { 249 | currentMotion := body.Velocity.Dot(&body.Velocity) + body.Rotation.Dot(&body.Rotation) 250 | bias := m.Real(math.Pow(0.5, float64(duration))) 251 | body.motion = bias*body.motion + (1.0-bias)*currentMotion 252 | 253 | if body.motion < sleepEpsilon { 254 | body.SetAwake(false) 255 | } else if body.motion > 10*sleepEpsilon { 256 | body.motion = 10 * sleepEpsilon 257 | } 258 | } 259 | } 260 | 261 | // CalculateDerivedData internal data from public data members. 262 | // 263 | // NOTE: This should be called after the RigidBody's state is alterted 264 | // directly by client code; it is called automatically during integration. 265 | // 266 | // Particularly, call this after modifying: 267 | // Position, Orientation 268 | func (body *RigidBody) CalculateDerivedData() { 269 | body.Orientation.Normalize() 270 | body.transform.SetAsTransform(&body.Position, &body.Orientation) 271 | transformInertiaTensor(&body.inverseInertiaTensorWorld, &body.InverseInertiaTensor, &body.transform) 272 | } 273 | 274 | // transformInertiaTensor is an inernal function to do an inertia tensor transform. 275 | func transformInertiaTensor(iitWorld *m.Matrix3, iitBody *m.Matrix3, rotmat *m.Matrix3x4) { 276 | var t4 = rotmat[0]*iitBody[0] + rotmat[3]*iitBody[1] + rotmat[6]*iitBody[2] 277 | var t9 = rotmat[0]*iitBody[3] + rotmat[3]*iitBody[4] + rotmat[6]*iitBody[5] 278 | var t14 = rotmat[0]*iitBody[6] + rotmat[3]*iitBody[7] + rotmat[6]*iitBody[8] 279 | 280 | var t28 = rotmat[1]*iitBody[0] + rotmat[4]*iitBody[1] + rotmat[7]*iitBody[2] 281 | var t33 = rotmat[1]*iitBody[3] + rotmat[4]*iitBody[4] + rotmat[7]*iitBody[5] 282 | var t38 = rotmat[1]*iitBody[6] + rotmat[4]*iitBody[7] + rotmat[7]*iitBody[8] 283 | 284 | var t52 = rotmat[2]*iitBody[0] + rotmat[5]*iitBody[1] + rotmat[8]*iitBody[2] 285 | var t57 = rotmat[2]*iitBody[3] + rotmat[5]*iitBody[4] + rotmat[8]*iitBody[5] 286 | var t62 = rotmat[2]*iitBody[6] + rotmat[5]*iitBody[7] + rotmat[8]*iitBody[8] 287 | 288 | iitWorld[0] = t4*rotmat[0] + t9*rotmat[3] + t14*rotmat[6] 289 | iitWorld[3] = t4*rotmat[1] + t9*rotmat[4] + t14*rotmat[7] 290 | iitWorld[6] = t4*rotmat[2] + t9*rotmat[5] + t14*rotmat[8] 291 | 292 | iitWorld[1] = t28*rotmat[0] + t33*rotmat[3] + t38*rotmat[6] 293 | iitWorld[4] = t28*rotmat[1] + t33*rotmat[4] + t38*rotmat[7] 294 | iitWorld[7] = t28*rotmat[2] + t33*rotmat[5] + t38*rotmat[8] 295 | 296 | iitWorld[2] = t52*rotmat[0] + t57*rotmat[3] + t62*rotmat[6] 297 | iitWorld[5] = t52*rotmat[1] + t57*rotmat[4] + t62*rotmat[7] 298 | iitWorld[8] = t52*rotmat[2] + t57*rotmat[5] + t62*rotmat[8] 299 | } 300 | --------------------------------------------------------------------------------