├── hero.lua ├── license └── readme.md /hero.lua: -------------------------------------------------------------------------------- 1 | local lp = love.physics 2 | 3 | local function removeDestroyed (t) 4 | if not t then return {} end 5 | for i = #t, 1, -1 do 6 | if t[i]:isDestroyed() then 7 | table.remove(t, i) 8 | end 9 | end 10 | return t 11 | end 12 | 13 | -- Shape 14 | 15 | local function ChainShape (t) 16 | local shape = lp.newChainShape(false, t.points) 17 | shape:setNextVertex(t.nextVertex[1], t.nextVertex[2]) 18 | shape:setPreviousVertex(t.previousVertex[1], t.previousVertex[2]) 19 | return shape 20 | end 21 | 22 | local function CircleShape (t) 23 | return lp.newCircleShape(t.point[1], t.point[2], t.radius) 24 | end 25 | 26 | local function EdgeShape (t) 27 | return lp.newEdgeShape(t.points[1], t.points[2], t.points[3], t.points[4]) 28 | end 29 | 30 | local function PolygonShape (t) 31 | return lp.newPolygonShape(t.points) 32 | end 33 | 34 | local shapeByType = { 35 | chain = ChainShape, 36 | circle = CircleShape, 37 | edge = EdgeShape, 38 | polygon = PolygonShape, 39 | } 40 | 41 | local function Shape (t, body) 42 | local shape = shapeByType[t.type](t, body) 43 | 44 | t.shape = shape 45 | 46 | return shape 47 | end 48 | 49 | local function ChainShapeState (shape) 50 | return { 51 | points = { shape:getPoints() }, 52 | nextVertex = { shape:getNextVertex() }, 53 | previousVertex = { shape:getPreviousVertex() }, 54 | } 55 | end 56 | 57 | local function CircleShapeState (shape) 58 | return { 59 | point = { shape:getPoint() }, 60 | radius = shape:getRadius(), 61 | } 62 | end 63 | 64 | local function EdgeShapeState (shape) 65 | return { 66 | points = { shape:getPoints() }, 67 | } 68 | end 69 | 70 | local function PolygonShapeState (shape) 71 | return { 72 | points = { shape:getPoints() }, 73 | } 74 | end 75 | 76 | local shapeStateByType = { 77 | chain = ChainShapeState, 78 | circle = CircleShapeState, 79 | edge = EdgeShapeState, 80 | polygon = PolygonShapeState, 81 | } 82 | 83 | local function ShapeState (shape) 84 | local shapeType = shape:getType() 85 | local t = shapeStateByType[shapeType](shape) 86 | 87 | t.type = shapeType 88 | 89 | return t 90 | end 91 | 92 | -- Fixture 93 | 94 | local function Fixture (t, body) 95 | local shape = Shape(t.shapeState) 96 | local fixture = lp.newFixture(body, shape) 97 | 98 | fixture:setCategory(t.category) 99 | fixture:setDensity(t.density) 100 | fixture:setFilterData(t.filterData[1], t.filterData[2], t.filterData[3]) 101 | fixture:setFriction(t.friction) 102 | fixture:setGroupIndex(t.groupIndex) 103 | fixture:setMask(t.mask) 104 | fixture:setRestitution(t.restitution) 105 | fixture:setSensor(t.sensor) 106 | fixture:setUserData(t.userData) 107 | 108 | body:resetMassData() 109 | 110 | t.fixture = fixture 111 | 112 | return fixture 113 | end 114 | 115 | local function FixtureState (fixture) 116 | return { 117 | id = tostring(fixture), 118 | category = fixture:getCategory(), 119 | density = fixture:getDensity(), 120 | filterData = { fixture:getFilterData() }, -- categories, mask, group 121 | friction = fixture:getFriction(), 122 | groupIndex = fixture:getGroupIndex(), 123 | mask = { fixture:getMask() }, 124 | restitution = fixture:getRestitution(), 125 | sensor = fixture:isSensor(), 126 | userData = fixture:getUserData(), 127 | 128 | shapeState = ShapeState(fixture:getShape()), 129 | } 130 | end 131 | 132 | -- Body 133 | 134 | local function Body (t, world) 135 | local body = lp.newBody(world, t.x, t.y, t.type) 136 | 137 | body:setActive(t.active) 138 | body:setAngle(t.angle) 139 | body:setAngularDamping(t.angularDamping) 140 | body:setAngularVelocity(t.angularVelocity) 141 | body:setAwake(t.awake) 142 | body:setBullet(t.bullet) 143 | body:setFixedRotation(t.fixedRotation) 144 | body:setGravityScale(t.gravityScale) 145 | body:setInertia(t.inertia) 146 | body:setLinearDamping(t.linearDamping) 147 | body:setLinearVelocity(t.linearVelocity[1], t.linearVelocity[2]) 148 | body:setMass(t.mass) 149 | -- body:setMassData(t.massData[1], t.massData[2], t.massData[3], t.massData[4]) 150 | body:setSleepingAllowed(t.sleepingAllowed) 151 | --body:setType(t.type) 152 | body:setUserData(t.userData) 153 | --body:setX(t.x) 154 | --body:setY(t.y) 155 | 156 | for i, fixtureState in ipairs(t.fixtureStates) do 157 | Fixture(fixtureState, body) 158 | end 159 | 160 | t.body = body 161 | 162 | return body 163 | end 164 | 165 | local function BodyState (body) 166 | local fixtureStates = {} 167 | 168 | local fixtures = removeDestroyed(body:getFixtureList()) 169 | 170 | for i, fixture in ipairs(fixtures) do 171 | fixtureStates[i] = FixtureState(fixture) 172 | end 173 | 174 | return { 175 | id = tostring(body), 176 | -- members 177 | active = body:isActive(), 178 | angle = body:getAngle(), 179 | angularDamping = body:getAngularDamping(), 180 | angularVelocity = body:getAngularVelocity(), 181 | awake = body:isAwake(), 182 | bullet = body:isBullet(), 183 | fixedRotation = body:isFixedRotation(), 184 | gravityScale = body:getGravityScale(), 185 | inertia = body:getInertia(), 186 | linearDamping = body:getLinearDamping(), 187 | linearVelocity = { body:getLinearVelocity() }, 188 | mass = body:getMass(), 189 | massData = { body:getMassData() }, -- x, y, mass, inertia 190 | -- position = { body:getPosition() }, 191 | sleepingAllowed = body:isSleepingAllowed(), 192 | type = body:getType(), 193 | userData = body:getUserData(), 194 | x = body:getX(), 195 | y = body:getY(), 196 | -- children 197 | fixtureStates = fixtureStates, 198 | } 199 | end 200 | 201 | -- Joint 202 | 203 | local function DistanceJoint (t, bodyMap, jointMap) 204 | -- body1, body2, x1, y1, x2, y2, collideConnected 205 | local joint = lp.newDistanceJoint( 206 | bodyMap[t.bodies[1]].body, bodyMap[t.bodies[2]].body, 207 | t.anchors[1], t.anchors[2], t.anchors[3], t.anchors[4], 208 | t.collideConnected) 209 | 210 | joint:setDampingRatio(t.dampingRatio) 211 | joint:setFrequency(t.frequency) 212 | joint:setLength(t.length) 213 | 214 | return joint 215 | end 216 | 217 | local function FrictionJoint (t, bodyMap, jointMap) 218 | -- body1, body2, x1, y1, x2, y2, collideConnected 219 | local joint = lp.newFrictionJoint( 220 | bodyMap[t.bodies[1]].body, bodyMap[t.bodies[2]].body, 221 | t.anchors[1], t.anchors[2], t.anchors[3], t.anchors[4], 222 | t.collideConnected) 223 | 224 | joint:setMaxForce(t.maxForce) 225 | joint:setMaxTorque(t.maxTorque) 226 | 227 | return joint 228 | end 229 | 230 | local function GearJoint (t, bodyMap, jointMap) 231 | -- nyi 232 | end 233 | 234 | local function MotorJoint (t, bodyMap, jointMap) 235 | -- body1, body2, correctionFactor, collideConnected 236 | local joint = lp.newMotorJoint( 237 | bodyMap[t.bodies[1]].body, bodyMap[t.bodies[2]].body, 238 | t.correctionFactor, 239 | t.collideConnected) 240 | 241 | joint:setAngularOffset(t.angularOffset) 242 | joint:setLinearOffset(t.linearOffset[1], t.linearOffset[2]) 243 | joint:setMaxForce(t.maxForce) 244 | joint:setMaxTorque(t.maxTorque) 245 | 246 | return joint 247 | end 248 | 249 | local function MouseJoint (t, bodyMap, jointMap) 250 | -- body, x, y 251 | local joint = lp.newMouseJoint( 252 | bodyMap[t.bodies[1]].body, 253 | t.target[1], t.target[2]) 254 | 255 | joint:setDampingRatio(t.dampingRatio) 256 | joint:setFrequency(t.frequency) 257 | joint:setMaxForce(t.maxForce) 258 | 259 | return joint 260 | end 261 | 262 | local function PrismaticJoint (t, bodyMap, jointMap) 263 | -- body1, body2, x1, y1, x2, y2, ax, ay, collideConnected, referenceAngle 264 | local joint = lp.newPrismaticJoint( 265 | bodyMap[t.bodies[1]].body, bodyMap[t.bodies[2]].body, 266 | t.anchors[1], t.anchors[2], t.anchors[3], t.anchors[4], 267 | t.axis[1], t.axis[2], 268 | t.collideConnected, t.referenceAngle) 269 | 270 | joint:setLowerLimit(t.lowerLimit) 271 | joint:setMaxMotorForce(t.maxMotorForce) 272 | joint:setMotorEnabled(t.motorEnabled) 273 | joint:setMotorSpeed(t.motorSpeed) 274 | joint:setUpperLimit(t.upperLimit) 275 | joint:setLimitsEnabled(t.limitsEnabled) 276 | 277 | return joint 278 | end 279 | 280 | local function PulleyJoint (t, bodyMap, jointMap) 281 | -- body1, body2, gx1, gy1, gx2, gy2, x1, y1, x2, y2, ratio, collideConnected 282 | local joint = lp.newPulleyJoint( 283 | bodyMap[t.bodies[1]].body, bodyMap[t.bodies[2]].body, 284 | t.groundAnchors[1], t.groundAnchors[2], 285 | t.groundAnchors[3], t.groundAnchors[4], 286 | t.anchors[1], t.anchors[2], t.anchors[3], t.anchors[4], 287 | t.ratio, 288 | t.collideConnected) 289 | 290 | return joint 291 | end 292 | 293 | local function RevoluteJoint (t, bodyMap, jointMap) 294 | -- body1, body2, x1, y1, x2, y2, collideConnected, referenceAngle 295 | local joint = lp.newRevoluteJoint( 296 | bodyMap[t.bodies[1]].body, bodyMap[t.bodies[2]].body, 297 | t.anchors[1], t.anchors[2], t.anchors[3], t.anchors[4], 298 | t.collideConnected, t.referenceAngle) 299 | 300 | joint:setLowerLimit(t.lowerLimit) 301 | joint:setMaxMotorTorque(t.maxMotorTorque) 302 | joint:setMotorEnabled(t.motorEnabled) 303 | joint:setMotorSpeed(t.motorSpeed) 304 | joint:setUpperLimit(t.upperLimit) 305 | joint:setLimitsEnabled(t.limitsEnabled) 306 | 307 | return joint 308 | end 309 | 310 | local function RopeJoint (t, bodyMap, jointMap) 311 | -- body1, body2, x1, y1, x2, y2, maxLength, collideConnected 312 | local joint = lp.newRopeJoint( 313 | bodyMap[t.bodies[1]].body, bodyMap[t.bodies[2]].body, 314 | t.anchors[1], t.anchors[2], t.anchors[3], t.anchors[4], 315 | t.maxLength, 316 | t.collideConnected) 317 | 318 | return joint 319 | end 320 | 321 | local function WeldJoint (t, bodyMap, jointMap) 322 | -- body1, body2, x1, y1, x2, y2, collideConnected, referenceAngle 323 | local joint = lp.newWeldJoint( 324 | bodyMap[t.bodies[1]].body, bodyMap[t.bodies[2]].body, 325 | t.anchors[1], t.anchors[2], t.anchors[3], t.anchors[4], 326 | t.collideConnected, t.referenceAngle) 327 | 328 | joint:setDampingRatio(t.dampingRatio) 329 | joint:setFrequency(t.frequency) 330 | 331 | return joint 332 | end 333 | 334 | local function WheelJoint (t, bodyMap, jointMap) 335 | -- body1, body2, x1, y1, x2, y2, ax, ay, collideConnected 336 | local joint = lp.newWheelJoint( 337 | bodyMap[t.bodies[1]].body, bodyMap[t.bodies[2]].body, 338 | t.anchors[1], t.anchors[2], t.anchors[3], t.anchors[4], 339 | t.axis[1], t.axis[2], 340 | t.collideConnected) 341 | 342 | joint:setMaxMotorTorque(t.maxMotorTorque) 343 | joint:setMotorEnabled(t.motorEnabled) 344 | joint:setMotorSpeed(t.motorSpeed) 345 | joint:setSpringDampingRatio(t.springDampingRatio) 346 | joint:setSpringFrequency(t.springFrequency) 347 | 348 | return joint 349 | end 350 | 351 | local jointByType = { 352 | distance = DistanceJoint, 353 | friction = FrictionJoint, 354 | gear = GearJoint, 355 | motor = MotorJoint, 356 | mouse = MouseJoint, 357 | prismatic = PrismaticJoint, 358 | pulley = PulleyJoint, 359 | revolute = RevoluteJoint, 360 | rope = RopeJoint, 361 | weld = WeldJoint, 362 | wheel = WheelJoint, 363 | } 364 | 365 | local function Joint (t, bodyMap, jointMap) 366 | local joint = jointByType[t.type](t, bodyMap, jointMap) 367 | 368 | joint:setUserData(t.userData) 369 | 370 | t.joint = joint 371 | 372 | return joint 373 | end 374 | 375 | -- create joint states by joint type 376 | 377 | local function DistanceJointState (joint, bodyMap, jointMap) 378 | return { 379 | dampingRatio = joint:getDampingRatio(), 380 | frequency = joint:getFrequency(), 381 | length = joint:getLength(), 382 | } 383 | end 384 | 385 | local function FrictionJointState (joint, bodyMap, jointMap) 386 | return { 387 | maxForce = joint:getMaxForce(), 388 | maxTorque = joint:getMaxTorque(), 389 | } 390 | end 391 | 392 | local function GearJointState (joint, bodyMap, jointMap) 393 | local jointA, jointB = joint:getJoints() 394 | return { 395 | joints = { jointMap[jointA], jointMap[jointB] }, 396 | ratio = joint:getRatio(), 397 | } 398 | end 399 | 400 | local function MotorJointState (joint, bodyMap, jointMap) 401 | return { 402 | angularOffset = joint:getAngularOffset(), 403 | linearOffset = { joint:getLinearOffset() }, 404 | maxForce = joint:getMaxForce(), 405 | maxTorque = joint:getMaxTorque(), 406 | correctionFactor = joint:getCorrectionFactor(), 407 | } 408 | end 409 | 410 | local function MouseJointState (joint, bodyMap, jointMap) 411 | return { 412 | dampingRatio = joint:getDampingRatio(), 413 | frequency = joint:getFrequency(), 414 | maxForce = joint:getMaxForce(), 415 | target = { joint:getTarget() }, 416 | } 417 | end 418 | 419 | local function PrismaticJointState (joint, bodyMap, jointMap) 420 | return { 421 | axis = { joint:getAxis() }, 422 | lowerLimit = joint:getLowerLimit(), 423 | maxMotorForce = joint:getMaxMotorForce(), 424 | motorSpeed = joint:getMotorSpeed(), 425 | upperLimit = joint:getUpperLimit(), 426 | limitsEnabled = joint:hasLimitsEnabled(), 427 | motorEnabled = joint:isMotorEnabled(), 428 | referenceAngle = joint:getReferenceAngle(), 429 | } 430 | end 431 | 432 | local function PulleyJointState (joint, bodyMap, jointMap) 433 | return { 434 | groundAnchors = { joint:getGroundAnchors() }, 435 | ratio = joint:getRatio(), 436 | } 437 | end 438 | 439 | local function RevoluteJointState (joint, bodyMap, jointMap) 440 | return { 441 | lowerLimit = joint:getLowerLimit(), 442 | maxMotorTorque = joint:getMaxMotorTorque(), 443 | motorSpeed = joint:getMotorSpeed(), 444 | upperLimit = joint:getUpperLimit(), 445 | limitsEnabled = joint:hasLimitsEnabled(), 446 | motorEnabled = joint:isMotorEnabled(), 447 | referenceAngle = joint:getReferenceAngle(), 448 | } 449 | end 450 | 451 | local function RopeJointState (joint, bodyMap, jointMap) 452 | return { 453 | maxLength = joint:getMaxLength(), 454 | } 455 | end 456 | 457 | local function WeldJointState (joint, bodyMap, jointMap) 458 | return { 459 | dampingRatio = joint:getDampingRatio(), 460 | frequency = joint:getFrequency(), 461 | referenceAngle = joint:getReferenceAngle(), 462 | } 463 | end 464 | 465 | local function WheelJointState (joint, bodyMap, jointMap) 466 | return { 467 | axis = { joint:getAxis() }, 468 | maxMotorTorque = joint:getMaxMotorTorque(), 469 | motorSpeed = joint:getMotorSpeed(), 470 | springDampingRatio = joint:getSpringDampingRatio(), 471 | springFrequency = joint:getSpringFrequency(), 472 | motorEnabled = joint:isMotorEnabled(), 473 | } 474 | end 475 | 476 | local jointStateByType = { 477 | distance = DistanceJointState, 478 | friction = FrictionJointState, 479 | gear = GearJointState, 480 | motor = MotorJointState, 481 | mouse = MouseJointState, 482 | prismatic = PrismaticJointState, 483 | pulley = PulleyJointState, 484 | revolute = RevoluteJointState, 485 | rope = RopeJointState, 486 | weld = WeldJointState, 487 | wheel = WheelJointState, 488 | } 489 | 490 | local function JointState (joint, bodyMap, jointMap) 491 | local t = jointStateByType[joint:getType()](joint, bodyMap, jointMap) 492 | 493 | if bodyMap then 494 | local bodyA, bodyB = joint:getBodies() 495 | t.bodies = { bodyMap[bodyA], bodyMap[bodyB] } 496 | end 497 | 498 | t.anchors = { joint:getAnchors() } 499 | t.collideConnected = joint:getCollideConnected() 500 | t.type = joint:getType() 501 | t.userData = joint:getUserData() 502 | t.id = tostring(joint) 503 | 504 | return t 505 | end 506 | 507 | -- World 508 | 509 | local function World (t, world) 510 | local bodyMap, jointMap = {}, {} 511 | local lookup = {} 512 | 513 | world = world or lp.newWorld() 514 | world:setGravity(t.gravity[1], t.gravity[2]) 515 | world:setSleepingAllowed(t.sleepingAllowed) 516 | 517 | -- index all bodies and add them to the world 518 | for i, bodyState in ipairs(t.bodyStates) do 519 | bodyMap[i] = bodyState 520 | lookup[bodyState.id] = Body(bodyState, world) 521 | for i, fixtureState in ipairs(bodyState.fixtureStates)do 522 | lookup[fixtureState.id] = fixtureState.fixture 523 | end 524 | end 525 | 526 | -- first pass over joints; index them all 527 | for i, jointState in ipairs(t.jointStates) do 528 | jointMap[i] = jointState 529 | end 530 | 531 | -- second pass over joints; add them to the world 532 | for i, jointState in ipairs(t.jointStates) do 533 | lookup[jointState.id] = Joint(jointState, bodyMap, jointMap) 534 | end 535 | 536 | return world, lookup 537 | end 538 | 539 | local function sortGears (a, b) 540 | return (a:getType() ~= 'gear' and b:getType() == 'gear') 541 | or tostring(a) < tostring(b) 542 | end 543 | 544 | local function WorldState (world) 545 | local bodies = removeDestroyed(world:getBodyList()) 546 | local joints = removeDestroyed(world:getJointList()) 547 | local bodyStates, bodyMap, jointStates, jointMap = {}, {}, {}, {} 548 | 549 | for i, body in ipairs(bodies) do 550 | bodyMap[body] = i 551 | bodyStates[i] = BodyState(body) 552 | end 553 | 554 | table.sort(joints, sortGears) 555 | 556 | for i, joint in ipairs(joints) do 557 | jointMap[joint] = i 558 | end 559 | 560 | for i, joint in ipairs(joints) do 561 | jointStates[i] = JointState(joint, bodyMap, jointMap) 562 | end 563 | 564 | return { 565 | -- members 566 | gravity = { world:getGravity() }, 567 | sleepingAllowed = world:isSleepingAllowed(), 568 | -- children 569 | bodyStates = bodyStates, 570 | jointStates = jointStates, 571 | } 572 | end 573 | 574 | return { 575 | Body = Body, 576 | BodyState = BodyState, 577 | Fixture = Fixture, 578 | FixtureState = FixtureState, 579 | Joint = Joint, 580 | JointState = JointState, 581 | World = World, 582 | WorldState = WorldState, 583 | load = World, 584 | save = WorldState, 585 | } 586 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # hero 2 | Saves the (love.physics) world. 3 | 4 | ## usage 5 | 6 | Call `Hero.save(world)` to save a world to a serializable table. Call `Hero.load(worldState)` to load a saved world. 7 | --------------------------------------------------------------------------------