Navigation

    Fuze Arena Logo
    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Popular
    • Users
    • Groups
    • Help
    • Discord

    Coding for Fun: Measuring Models and Making Advanced Collision Responses

    Functions
    dimensions collision measure response
    4
    11
    937
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Chronos
      Chronos last edited by Chronos

      This is going to be a long one, which adds greater likelihood of transcription errors, but it is totally worth the trouble.

      With objectIntersect() alone, you are limited to pretty much making things explode on impact or stop moving entirely. However, you can do much much more if you figure out where the boundary faces are, when the collision occurs, and where it should move.

      It is a multi-step process that uses the objectIntersect function like crazy, and with so much involved, I apologize if there are any errors from where I retyped it.

      Here are the general steps:

      • Make a useful 3D object constructor using createSprite() so you can store your object properties in an object-oriented way.
      • Measure the your models to get the upper-front-left and lower-bottom-right corners.
      • Ram your objects into each other.
      • Adjust the objects' positions to find the point of impact.
      • Find the faces of the bounding box that collided.
      • Adjust the movement based on the remaining velocity projected onto the face.
      • Make sure the movement didn't mess up other collisions
      vector homogeneous_scale = {1,1,1}
      
      function createObject(model_name, pos, vel, sca, rot, rvel)
          // model_name is the Media name of the model.
          // pos is the initial position (vector).
          // vel is the initial velocity (vector).
          // sca is the scale vector of the object.
          // rot is the rotation vector of the object.
          // rvel is the rotational velocity (vector).
      
          // sprites allow for arbitrary "object-oriented" attributes.
          //  we won't be using any actual sprite features.
          var obj = createSprite()
          var handle = loadModel(model_name)
      
          // store the data
          obj.model_name = model_name
          obj.model = handle
          obj.pos = pos
          obj.vel = vel
          obj.rvel = rvel
          obj.sca = sca
      
          // initially place the object at the origin for measurement
          obj.obj = placeObject(handle, {}, obj.sca)
          // store the dimensions of the model here
          var dimensions = measurement(obj.obj)
          obj.dim = dimensions
      
          // set the real position now that measurement is done
          setObjectPos(obj.obj, obj.pos)
      
          return obj
      
      // define the local directions
      vector forward = {0,0,1}
      vector backward = {0,0,-1}
      vector leftward = {1,0,0}
      vector rightward = {-1,0,0}
      vector upward = {0,1,0}
      vector downward = {0,-1,0}
      // the order of the vectors in directions is _very_ important 
      //  for measurement() and collision_faces()!
      var directions = [leftward, rightward, upward, downward, forward, backward]
      int directions_length = len(directions)
      
      function measurement(object)
          // our upper and lower dimensions, respectively
          vector dimensionsA = {}
          vector dimensionsB = {}
          // the flattened cube we use to measure things
          var ruler = placeObject(cube, {}, {1,1,.00001})
          for i=0 to directions_length loop
              // the final position of the ruler has nonzero value on only one axis
              vector pos = measure_direction(object, ruler, directions[i])
              // so add that position to the respective dimension
              if i%2 then
                  dimensionsB += pos
              else
                  dimensionsA += pos
              endif
          repeat
          removeObject(ruler)
          // you cannot skip out on making it a variable to return: 
          //  it will crash if you return the array via "return [dimensionsA, dimensionsB]".
          var dims = [dimensionsA, dimensionsB]
          return dims
      
      function measure_direction(object, ruler, direction)
          var ruler_pos = direction/2
          // start at the origin, where there is definitely a collision
          setObjectPos(ruler, {})
          // set the ruler's flat side to be parallel to the face it will measure
          if direction.y then
              // I think the terminology is something like: avoid gimbal lock
              objectPointAt(ruler, forward)
              rotateObject(ruler, {1,0,0}, 90)
          else
              objectPointAt(ruler, direction)
          endif
          // move the ruler out in large steps until it doesn't touch the ruler
          while objectIntersect(object, ruler) loop
              ruler_pos *= 2
              setObjectPos(ruler, ruler_pos)
          repeat
          //refine the ruler position
          // start halfway between the point of no touching and definitely touching
          var t = 0.5
          // change t by dt/2, but go up or down based on whether there was a collision
          var dt = 0.5
          // the refined times of no intersection and intersection
          var no_intersect_t = 0
          var intersect_t = 1
      
          vector cpos = {}
          vector origin = {}
          // "binary search" for a very near the edge touching point
          while dt > 0.005 loop
              cpos = lerp(ruler_pos, origin, t)
              setObjectPos(ruler, cpos)
      
              // it is kind of cool to watch it measure stuff.
              // uncomment to watch
              /*drawObjects()
              printAt(0,0, direction, cpos)
              update()
              sleep(0.01)*/
      
              if objectIntersect(ruler, object) then
                  intersect_t = t
                  t -= dt/2
              else
                  no_intersect_t = t
                  t += dt/2
              endif
          repeat
          // get the last touching intersection
          t = intersect_t
          cpos = lerp(ruler_pos, origin, t)
          return cpos
      
      function sign(value)
          int sign = 0
          if value > 0 then
              sign = 1
          else
              if value < 0 then
                  sign = -1
              endif
          endif
          return sign
      
      
      function updateObj(obj, dt)
          // here, dt is a variable defined in the update loop
          // dt = deltaTime()
          setOld(obj)
      
          var rot = obj.rot+obj.rvel*dt
          for r=0 to 3 loop
              // avoid integer overflow (which would take a LOT of rotation)
              if abs(rot[r]) > 360 then
                  rot[r] -= 360*sign(rot[r])
              endif
          repeat
      
          obj.rot = rot
      
          // convert the velocity to world coordinates
          vector world_vel = rotate(obj.vel, obj.rot)
          obj.pos = obj.pos + world_vel*dt
          setObjectPos(obj.obj, obj.pos)
      
          // make the object face forward in the world
          vector world_dir = rotate(forward, obj.rot)
          // probably wouldn't like looking straight up
          // because the direction is normalized (and rotated normalized vectors are normalized),
          //  we only need to assess if y is 1
          if abs(world_dir.y) == 1 then
              objectPointAt(obj.obj, obj.pos+forward)
              rotateObject(obj.obj, {1,0,0}, sign(world_dir.y)*90)
          else
              objectPointAt(obj.obj, obj.pos+world_dir)
          endif
          return void
      
      function setOld(obj1)
          // set things up for collision detection
          obj1.opos = obj1.pos
          obj1.ovel = obj1.vel
          obj1.osca = obj1.sca
          obj1.orot = obj1.rot
          obj1.orvel = obj1.rvel
          return void
      
      function setOrbitCam(degrees, dist, height, lookat)
          var cam = lookat+{cos(degrees)*dist, height, sin(degrees*dist)}
          setCamera(cam, lookat)
          return void
      
      function collision_faces(objs)
          // given that a collision happened between 
          //  objs[0] and objs[1], AND you narrowed down the point of impact,
          //  find which faces are involved
          var all_faces = []
      
          for i=0 to 2 loop
              // from pos, move the object over by each dimension separately
              var inst = objs[i]
              var obj = inst.obj
              vector pos = inst.pos
              vector sca = inst.sca
              var dim = inst.dim
              vector rot = inst.rot
              // there's a face to check in every direction
              for d=0 to directions_length loop
                  // upper or lower corner (0 or 1)
                  int idx2 = int(d%2)
                  // what is the index for the positive direction?
                  //  i.e., right, up, or forward
                  //  obj.dim has negative values built-in to lower corner, 
                  //  so don't negate the negative by using a negative direction.
                  int idx3 = d-idx2
                  // x,y,z are 0,1,2
                  int idx = idx3/2
      
                  setObjectPos(obj, pos+rotate(directions[idx3]*dim[idx2], rot))
                  vector sca2 = sca
                  // flatten it somewhat
                  sca2[idx] = 0.02
                  setObjectScale(obj, sca2)
                  // see if the "face" (a flattened, shifted model) collides
                  faces[d] = objectIntersect(objs[0].obj, objs[1].obj)
              repeat
              // reset the features
              setObjectPos(obj, pos)
              setObjectScale(obj, sca)
              // store this object's face collision data
              all_faces[i] = faces
          repeat
          return all_faces
      
      function interpolateObjectRotation(obj, t)
          obj1.crot = lerp(obj1.orot, obj1.rot, t)
          var world_dir = rotate(forward, obj1.crot)
          if abs(world_dir.y) == 1 then
              objectPointAt(obj.obj, obj.cpos+forward)
              rotateObject(obj.obj, {1,0,0}, sign(world_dir.y)*90)
          else
              objectPointAt(obj.obj, obj.cpos+world_dir)
          endif
          return void
      
      function project(vec, plane_normal)
          l = length(plane_normal)
          return cross(plane_normal, cross(vec, plane_normal/l)/l)
      
      function place_objects(objs, t, final)
          // helper function for point of impact
          for i=0 to 2 loop
              objs[i].cpos = lerp(objs[i].opos, objs[i].pos, t)
              setObjectPos(objs[i].obj, objs[i].cpos)
      
              interpolateObjectRotation(objs[i],t)
      
              var sca = lerp(objs[i].osca, objs[i].sca, t)
              setObjectScale(objs[i].obj, sca)
      
              if final then
                  objs[i].pos = objs[i].cpos
                  objs[i].rot = objs[i].crot
                  objs[i].rvel = (objs[i].crot-objs[i].orot)*t
                  objs[i].sca = objs[i].sca
              endif
          repeat
          return void
      
      function separate(objs)
          // midpoint
          vector m = (objs[0].pos+objs[1].pos)/2
          while objectIntersect(objs[0].obj, objs[1].obj) loop
              // even split
              if length(objs[0].ovel) and length(objs[1].obj) then
                  objs[0].pos = m+ (objs[0].pos-m)*1.1
                  objs[1].pos = m+ (objs[1].pos-m)*1.1
              else
                  if length(objs[0].ovel) and !length(objs[1].obj) then
                      objs[0].pos = m+ (objs[0].pos-m)*1.1
                  else
                      if !length(objs[0].ovel) and length(objs[1].obj) then
                          objs[1].pos = m+ (objs[1].pos-m)*1.1
                      else
                          // neither moved
                          if !length(objs[0].ovel) and !length(objs[1].obj) then
                              objs[0].pos = m+ (objs[0].pos-m)*1.1
                              objs[1].pos = m+ (objs[1].pos-m)*1.1
                          endif
                      endif
                  endif
              endif
              setObjectPos(objs[0].obj, objs[0].pos)
              setObjectPos(objs[1].obj, objs[1].pos)
          repeat
          return void
      
      function pointOfImpact(obj1, obj2, loops, wdt)
          // this function assumes either that they started apart 
          //  or that one/both of them is/are movable
          float t = 0.0
          // were either of them moving?
          if length(obj1.ovel) or length(obj2.ovel) then
              t = 0.5
              var objs = [obj1, obj2]
              float dt = 0.5
              float intersect_t = 1
              float no_intersect_t = 0
      
              while dt > 0.05 loop
                  for i=0 to 2 loop
                      objs[i].cpos = lerp(objs[i].opos, objs[i].pos, t)
                      setObjectPos(objs[i].obj, objs[i].cpos)
                      setObjectScale(objs[i].obj, lerp(objs[i].osca, objs[i].sca, t))
                      interpolateObjectRotation(objs[i], t)
                  repeat
                  // finding the point of impact uses the 
                  //  same general formula as measuring the objects
                  if objectIntersect(obj1.obj, obj2.obj) then
                      intersect_t = t
                      t -= dt
                  else
                      no_intersect_t = t
                      t += dt
                  endif
                  dt/=2
              repeat
      
              t=intersect_t
              place_objects(objs, t, false)
              var faces = collision_faces(objs)
      
              t = no_intersect_t
              place_objects(objs, t, false)
      
              // if they are still together, then separate them
              // likely caused by objects starting together
              if objectIntersect(objs[0].obj, objs[1].obj) then
                  separate(objs)
                  objs[0].cpos = objs[0].pos
                  objs[1].cpos = objs[1].pos
              endif
              // try to rotate anyway
              var tmps = []
              var bools = [0,0]
              for i=0 to 2 loop
                  interpolateObjectRotation(objs[i], 1)
                  tmps[i] = objs[i].crot
                  if !objectIntersect(objs[0].obj, objs[1].obj) then
                      bools[i] = true
                  endif
              repeat
      
              place_objects(objs, t, true)
              for i=0 to 2 loop
                  if bools[i] then
                      objs[i].rot = tmps[i]
                  endif
              repeat
      
              if loops == 1 then
                  for i=0 to 2 loop
                      if length(objs[i].vel)!=0 then
                          // get the remaining velocity in world coordinates
                          vector vel = rotate(objs[i].vel*(wdt-wdt*t), objs[i].rot)
                          vector projected_move = {}
                          var other = !i
                          var other_faces = faces[other]
                          var count = 0
                          for f=0 to directions_length loop
                              // if they hit the face
                              if other_faces[f] then
                                  // project onto normal from world coordinates direction
                                  vector wd = rotate(directions[f], objs[other].rot)
                                  vector p = project(vel, wd)
                                  // see if it causes a collision (measurements and floats may be imprecise)
                                  // plus, edge cases may occur for the face detection
                                  setObjectPos(objs[i].obj, objs[i].pos+(p+projected_move)/(count+1))
                                  if !objectIntersect(objs[0].obj, objs[1].obj) then
                                      projected_move += p
                                      count += 1
                                  endif
                              endif
                          repeat
                          if count > 0 then
                              // move by average projected velocity
                              objs[i].pos = objs[i].pos + projected_move/count
                          endif
                          setObjectPos(objs[i].obj, objs[i].pos)
                      endif
                      // the velocity is the amount it moved in the frame given the 
                      //  amount of time that passed, but transformed to local coordinates
                      objs[i].vel = unrotate((objs[i].pos)/wdt, objs[i].rot)
                  repeat
              endif
              // if they are still together, then separate them
              if objectIntersect(objs[0].obj, objs[1].obj) then
                  separate(objs)
              endif
          endif
          return t
      
      
      
      
      function matrix_transform(vec, mat)
          // matrices are defined here as an array of row vectors
          // i.e., [{1,0,0}, {0,1,0}, {0,0,1}] for a 3x3 identity
          vector v = {}
          for i=0 to len(mat) loop
              v[i] = dot(mat[i], vec)
          repeat
          return v
      
      function rotation_matX(rx)
          // make a rotation matrix for the x axis
          var xc = cos(rx)
          var xs = sin(rx)
          var rmx = [{1,0,0}, {0,xc,-xs}, {0, xs, xc}]
          return rmx
      
      function rotation_matY(ry)
          // make a rotation matrix for the y axis
          var yc = cos(ry)
          var ys = cos(ry)
          var rmy = [{yc,0,ys}, {0,1,0}, {-ys,0,yc}]
          return rmy
      
      function rotation_matZ(rz)
          // make a rotation matrix for the z axis
          var zc = cos(rz)
          var zs = sin(rz)
          var rmz = [{zc, -zs, 0}, {zs, zc, 0}, {0,0,1}]
          return rmz
      
      function rotateX(direction, degrees)
          vector new_dir = matrix_transform(direction, rotation_matX(degrees))
          return new_dir
      
      function rotateZ(direction, degrees)
          vector new_dir = matrix_transform(direction, rotation_matZ(degrees))
          return new_dir
      
      function rotateY(direction, degrees)
          // best one! rotate around Up
          vector new_dir = matrix_transform(direction, rotation_matY(degrees))
          return new_dir
      
      function rotate(direction, rot)
          // direction is a normal vector that specifies 
          //  facing (i.e., to use with objectPointAt)
          // rot is a vector of x, y, and z values
          var new_dir = direction
          // order of operations is important, I just like this order
          new_dir = rotateY(new_dir, rot.y)
          new_dir = rotateZ(new_dir, rot.z)
          new_dir = rotateX(new_dir, rot.x)
      
          return new_dir
      
      function unrotate(direction, rot)
          // go back to local coordinates
          var new_dir = direction
          // order of operations is important, and it is reversed for unrotate
          new_dir = rotateX(new_dir, -1*rot.x)
          new_dir = rotateZ(new_dir, -1*rot.z)
          new_dir = rotateY(new_dir, -1*rot.y)
          return new_dir
      
      // now let's get an example:
      car = createObject("Quaternius/NormalCar1", {0, 5.5, 0}, 
                          {}, homogeneous_scale, {}, {})
      car2 = createObject("Quaternius/NormalCar2", {-1, 1.5, 0}, 
                          {}, homogeneous_scale, {0,45,0}, {})
      street = createObject("Quaternius/Street_Empty", {}, {}, {10,1,10}, {}, {})
      
      height = 10
      angle = 0
      showObjectBounds(true, white, 1)
      setOrbitCam(angle, 10, height, {})
      light = pointShadowLight({0,4,2}, white, 100, 100)
      x = 0
      float t = 0
      var cars = [car, car2]
      var collides = [car, car2, street]
      
      loop
          dt = deltaTime()
          clear()
          c = controls(0)
          setLightPos(light, {x,4,2})
          if c.left then
              x = x-0.2
          endif
          if c.right then
              x = x+0.2
          endif
      
          
          if c.a then
              vector v = car.vel
              // increase speed in local coordinates! forward is z
              v.z += 0.1
              car.vel = v
          else
              if c.b then
                  vector v = car.vel
                  v.z -= 0.1
                  car.vel = v
              endif
          endif
      
          // rotate the car in the expected direction 
          //  with the left stick (if they are moving)
          vector r = car.rvel
          r.y = -c.lx*50*sign(car.vel.z)
          car.rvel = r
      
          // slow down and gravity
          for i = 0 to len(cars) loop
              var carA = cars[i]
              vector v = carA.vel
              if length(carA.vel) > 0 then
                  // gradual slowing
                  v -= normalize(v)*dt
                  carA.vel = v
              endif
              // gravity test, again, in local coordinates
              v.y -= .1
              carA.vel = v
          repeat
      
          for i=0 to len(collides) loop
              updateObj(collides[i])
          repeat
      
          angle += c.rx
          height += c.ry
      
          setOrbitCam(angle, 10, height, {})
          int collision = true
          int loops = 0
          // adjusting object positions due to collisions can cause more
          //  collisions with other objects, 
          //  so keep going until there are no collisions.
          while collision loop
              collision = false
              loops += 1
              for i=0 to len(collides) loop
                  var obj1 = collides[i]
                  for j=i+1 to len(collides) loop
                      var obj2 = collides[j]
                      if objectIntersect(obj1.obj, obj2.obj) then
                          collision = true
                          pointOfImpact(obj1, obj2, loops, dt)
                          showObjectBounds(true, red, 1)
                      endif
                  repeat
              repeat
          repeat
          drawObjects()
          update()
      repeat
      

      The project itself doesn't has the same comments, and has slight differences based on what I plan to implement (or things I thought could be slightly better as I was retyping): 5E1R7MNDN5

      Thanks for reading, and make something great.
      Jason

      1 Reply Last reply Reply Quote 2
      • PickleCatStars
        PickleCatStars F last edited by

        Wow, this is quite a lot to take in! I’ve had a similar thing on my to do list for quite some time, and it looks like you’ve done all I was hoping to do and more! I won’t be able to drag-and-drop this but I’ll be studying it for sure.

        Is there any advantage to using sprites as the base rather than a struct?

        1 Reply Last reply Reply Quote 1
        • Chronos
          Chronos last edited by

          I had memory leaks the first time I implemented something else with structs (it was when I did sprite splicing to add to the possible animations). I don't know if this was the problem exactly, but I think that structs were copied into each function instead of being passed by reference (without being deleted). FUZE4 silently crashed without an error (which isn't something it does if I load too many models), so it is a bit of conjecture on my part.

          Regardless, structs are limited to their preset attributes. I may try to extend it to allow optional spherical boundaries, which will add attributes that aren't always present (i.e., radius).

          Let me know if you have any questions, and thanks for your interest.
          Jason

          1 Reply Last reply Reply Quote 1
          • PickleCatStars
            PickleCatStars F last edited by

            Do you know about the ref keyword?

            1 Reply Last reply Reply Quote 0
            • Chronos
              Chronos last edited by

              I definitely tried to use it for the sprite-splicing related project, but since that was a while ago (and I reimplemented it with sprites too), I can't just post it as an example case. I'll try to make a short program that makes it crash that way with structs later.

              1 Reply Last reply Reply Quote 0
              • PickleCatStars
                PickleCatStars F last edited by

                I figured you’d already know about it. I just thought I’d mention it just in case.

                I’m excited to have a look at all of this here, especially curious about the rotational velocity stuff, which I had decided was ‘maybe sometime down the road, but not now’.

                I will definitely write back here if I have any questions. In the meantime, thanks again! :)

                1 Reply Last reply Reply Quote 0
                • Chronos
                  Chronos last edited by

                  Huh, I had all of the struct code for the sprites conveniently commented out because of the problem I'd had, but when I reintroduced them (and waited for a crash) nothing bad happened. Maybe I didn't wait long enough, or maybe there was a patch?

                  At any rate, I rescind my comment about the struct crash, for now xD.

                  Sprite splicing:
                  With structs N1WGDMNDN5
                  Without structs NSYMENNDN5

                  pianofire 1 Reply Last reply Reply Quote 2
                  • pianofire
                    pianofire Fuze Team @Chronos last edited by

                    @Chronos You have shared these programs but they are currently only visible to your Switch Friends. If you want them to be downloadable by code they have to be submitted for approval.Take a look here: https://fuzearena.com/forum/topic/725/new-community-sharing-on-fuze4.

                    1 Reply Last reply Reply Quote 1
                    • Chronos
                      Chronos last edited by

                      Done! Thanks for the tip.

                      1 Reply Last reply Reply Quote 1
                      • PickleCatStars
                        PickleCatStars F last edited by

                        Hiya! Just got around to downloading this, and I can tell straight away that it’s going to make my kids happy. They’re always shocked and offended when in my game, the player character can just walk straight through buildings as if they’re not there. Looking forward to having a good dive into this, thanks again :)

                        1 Reply Last reply Reply Quote 1
                        • joyrider3774
                          joyrider3774 last edited by

                          thanks soo much for this i use it to know objects dimensions for my projects well i use it to find the values i need, i don't use the code in my project

                          1 Reply Last reply Reply Quote 1
                          • First post
                            Last post