Walk cycle for a Blocky_Character!

…could come in handy!


function setup()
  scene = craft.scene()
  scene.sun.rotation = quat.eulerAngles(45, 355, 0)
  viewer.mode = FULLSCREEN
  
  obv = scene.camera:add(OrbitViewer, vec3(0,8,0), 30, 2, 120)
  obv.rx = 20
  obv.ry = 130
  
  parts = adventurerInPieces(scene)
  
  local offsets = {
    head     = vec3( 0,    0.75, 0),
    torso    = vec3( 0,    0,    0),
    leftArm  = vec3( 0.5,  0,    0),
    rightArm = vec3(-0.5,  0,    0),
    leftLeg  = vec3( 0.5, -0.75, 0),
    rightLeg = vec3(-0.5, -0.75, 0)
  }
  
  for name, e in pairs(parts) do
    e.position = offsets[name] or vec3(0,0,0)
  end
end

function update(dt)
  scene:update(dt)
end

function draw()
  update(DeltaTime)
  scene:draw()
end

function touched(t)
  obv:touched(t)
end

BODY_PARTS = {
  head     = {13,14,15,16,17,18,19,20,21,22,23,24},
  torso    = {1,2,3,4,5,6,7,8,9,10,11,12,49,52},
  leftArm  = {25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77},
  rightArm = {78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131},
  leftLeg  = {132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167},
  rightLeg = {168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203}
}

function adventurerInPieces(scene)
  local model    = craft.model(asset.builtin.Blocky_Characters.Adventurer)
  local material = craft.material(asset.builtin.Materials.Standard)
  material.map   = readImage(asset.builtin.Blocky_Characters.AdventurerSkin)
  
  local ind = model.indices
  local triangles = {}
  for i = 1, #ind, 3 do
    triangles[#triangles+1] = {i1=ind[i], i2=ind[i+1], i3=ind[i+2]}
  end
  
  local function buildMesh(triIndices)
    local pos, uv, col, norm = model.positions, model.uvs, model.colors, model.normals
    local newPos, newUV, newCol, newNorm, newInd, vertexMap = {}, {}, {}, {}, {}, {}
    local function addVertex(old)
      if vertexMap[old] then return vertexMap[old] end
      local new = #newPos + 1
      vertexMap[old] = new
      newPos[new] = pos[old]
      if uv   then newUV[new]   = uv[old]   end
      if col  then newCol[new]  = col[old]  end
      if norm then newNorm[new] = norm[old] end
      return new
    end
    for _, id in ipairs(triIndices) do
      local t = triangles[id]
      if t then
        table.insert(newInd, addVertex(t.i1))
        table.insert(newInd, addVertex(t.i2))
        table.insert(newInd, addVertex(t.i3))
      end
    end
    local m = craft.model()
    m.positions = newPos
    m.indices   = newInd
    if #newUV   > 0 then m.uvs     = newUV   end
    if #newCol  > 0 then m.colors  = newCol  end
    if #newNorm > 0 then m.normals = newNorm end
    return m
  end
  
  local parts = {}
  for name, triIndices in pairs(BODY_PARTS) do
    local e = scene:entity()
    e.model    = buildMesh(triIndices)
    e.material = material
    parts[name] = e
  end
  return parts
end


Now with walking!


require(asset.documents.Craft.Cameras)

function setup()
  scene = craft.scene()
  scene.sun.rotation = quat.eulerAngles(45, 355, 0)
  viewer.mode = FULLSCREEN
  
  obv = scene.camera:add(OrbitViewer, vec3(0,8,0), 30, 2, 120)
  obv.rx = 20
  obv.ry = 130
  
  rig = adventurerRig(scene)   

  function setupParameters()
    local function add(name, minV, maxV, defaultV)
      
      local startValue = readLocalData(name, defaultV)
      
      _G[name] = startValue
      
      parameter.number(name, minV, maxV, startValue, function(v)
        _G[name] = v
        saveLocalData(name, v)
      end)
    end
    
    add("ArmPivotX", 0, 1, 0.38)
    add("ArmPivotY", -1, 15, 10.66)
    add("ArmPivotZ", -1, 1, 0)
    
    add("LegPivotX", 0, 1, 0.18)
    add("LegPivotY", -1, 15, 6.86)
    add("LegPivotZ", -1, 1, 0)
    
    add("ArmSwing", 0, 90, 31.20)
    add("LegSwing", 0, 90, 38.95)
    add("WalkSpeed", 0, 10, 3)
  end
  
  setupParameters()
  
--[[  
  --settings for a normal walk:
  ArmPivotX = ArmPivotX or 0.38
  ArmPivotY = ArmPivotY or 10.66
  ArmPivotZ = ArmPivotZ or 0
  
  LegPivotX = LegPivotX or 0.18
  LegPivotY = LegPivotY or 6.86
  LegPivotZ = LegPivotZ or 0
  
  ArmSwing = ArmSwing or 31.20
  LegSwing = LegSwing or 38.95
  WalkSpeed = WalkSpeed or 5]]
end

function draw()
  
  animateWalk(rig, ElapsedTime)
  
  scene:update(DeltaTime)
  
  scene:draw()
  
end

function touched(t)
  obv:touched(t)
end

BODY_PARTS = {
  head     = {13,14,15,16,17,18,19,20,21,22,23,24},
  torso    = {1,2,3,4,5,6,7,8,9,10,11,12,49,52},
  leftArm  = {25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77},
  rightArm = {78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131},
  leftLeg  = {132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167},
  rightLeg = {168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203}
}

function adventurerInPieces(scene)
  local model    = craft.model(asset.builtin.Blocky_Characters.Adventurer)
  local material = craft.material(asset.builtin.Materials.Standard)
  material.map   = readImage(asset.builtin.Blocky_Characters.AdventurerSkin)
  
  local ind = model.indices
  local triangles = {}
  for i = 1, #ind, 3 do
    triangles[#triangles+1] = {i1=ind[i], i2=ind[i+1], i3=ind[i+2]}
  end
  
  local function buildMesh(triIndices)
    local pos, uv, col, norm = model.positions, model.uvs, model.colors, model.normals
    local newPos, newUV, newCol, newNorm, newInd, vertexMap = {}, {}, {}, {}, {}, {}
    local function addVertex(old)
      if vertexMap[old] then return vertexMap[old] end
      local new = #newPos + 1
      vertexMap[old] = new
      newPos[new] = pos[old]
      if uv   then newUV[new]   = uv[old]   end
      if col  then newCol[new]  = col[old]  end
      if norm then newNorm[new] = norm[old] end
      return new
    end
    for _, id in ipairs(triIndices) do
      local t = triangles[id]
      if t then
        table.insert(newInd, addVertex(t.i1))
        table.insert(newInd, addVertex(t.i2))
        table.insert(newInd, addVertex(t.i3))
      end
    end
    local m = craft.model()
    m.positions = newPos
    m.indices   = newInd
    if #newUV   > 0 then m.uvs     = newUV   end
    if #newCol  > 0 then m.colors  = newCol  end
    if #newNorm > 0 then m.normals = newNorm end
    return m
  end
  
  local parts = {}
  for name, triIndices in pairs(BODY_PARTS) do
    local e = scene:entity()
    e.model    = buildMesh(triIndices)
    e.material = material
    parts[name] = e
  end
  return parts
end

function adventurerRig(scene)
  
  local model = craft.model(asset.builtin.Blocky_Characters.Adventurer)
  
  local material = craft.material(asset.builtin.Materials.Standard)
  material.map = readImage(asset.builtin.Blocky_Characters.AdventurerSkin)
  
  local ind = model.indices
  
  local triangles = {}
  
  for i = 1, #ind, 3 do
    triangles[#triangles+1] = {
      i1 = ind[i],
      i2 = ind[i+1],
      i3 = ind[i+2]
    }
  end
  
  local function buildMesh(triIndices)
    
    local pos  = model.positions
    local uv   = model.uvs
    local col  = model.colors
    local norm = model.normals
    
    local newPos  = {}
    local newUV   = {}
    local newCol  = {}
    local newNorm = {}
    local newInd  = {}
    
    local vertexMap = {}
    
    local function addVertex(old)
      
      local existing = vertexMap[old]
      
      if existing then
        return existing
      end
      
      local new = #newPos + 1
      
      vertexMap[old] = new
      
      newPos[new] = pos[old]
      
      if uv then newUV[new] = uv[old] end
      if col then newCol[new] = col[old] end
      if norm then newNorm[new] = norm[old] end
      
      return new
    end
    
    for _, triId in ipairs(triIndices) do
      
      local t = triangles[triId]
      
      if t then
        table.insert(newInd, addVertex(t.i1))
        table.insert(newInd, addVertex(t.i2))
        table.insert(newInd, addVertex(t.i3))
      end
    end
    
    local m = craft.model()
    
    m.positions = newPos
    m.indices = newInd
    
    if #newUV > 0 then m.uvs = newUV end
    if #newCol > 0 then m.colors = newCol end
    if #newNorm > 0 then m.normals = newNorm end
    
    return m
  end
  
  --------------------------------------------------
  -- root
  --------------------------------------------------
  
  local root = scene:entity()
  
  --------------------------------------------------
  -- pivots
  --------------------------------------------------
  
  local torsoPivot = scene:entity()
  torsoPivot.parent = root
  
  local headPivot = scene:entity()
  headPivot.parent = torsoPivot
  
  local leftArmPivot = scene:entity()
  leftArmPivot.parent = torsoPivot
  
  local rightArmPivot = scene:entity()
  rightArmPivot.parent = torsoPivot
  
  local leftLegPivot = scene:entity()
  leftLegPivot.parent = torsoPivot
  
  local rightLegPivot = scene:entity()
  rightLegPivot.parent = torsoPivot
  
  --------------------------------------------------
  -- meshes
  --------------------------------------------------
  
  local meshes = {}
  
  for name, triIndices in pairs(BODY_PARTS) do
    
    local e = scene:entity()
    
    e.model = buildMesh(triIndices)
    e.material = material
    
    meshes[name] = e
  end
  
  meshes.torso.parent    = torsoPivot
  meshes.head.parent     = headPivot
  
  meshes.leftArm.parent  = leftArmPivot
  meshes.rightArm.parent = rightArmPivot
  
  meshes.leftLeg.parent  = leftLegPivot
  meshes.rightLeg.parent = rightLegPivot
  
  --------------------------------------------------
  -- joint locations
  --------------------------------------------------
  
  leftArmPivot.position  = vec3( 0.38, 0.40, 0)
  rightArmPivot.position = vec3(-0.38, 0.40, 0)
  
  leftLegPivot.position  = vec3( 0.18,-0.50, 0)
  rightLegPivot.position = vec3(-0.18,-0.50, 0)
  
  headPivot.position     = vec3(0,0.72,0)
  
  --------------------------------------------------
  -- compensate mesh positions
  --------------------------------------------------
  
  meshes.leftArm.position  = -leftArmPivot.position
  meshes.rightArm.position = -rightArmPivot.position
  
  meshes.leftLeg.position  = -leftLegPivot.position
  meshes.rightLeg.position = -rightLegPivot.position
  
  meshes.head.position     = -headPivot.position
  
  --------------------------------------------------
  -- return rig
  --------------------------------------------------
  
  return {
    root = root,
    
    torsoPivot = torsoPivot,
    headPivot = headPivot,
    
    leftArmPivot = leftArmPivot,
    rightArmPivot = rightArmPivot,
    
    leftLegPivot = leftLegPivot,
    rightLegPivot = rightLegPivot,
    
    meshes = meshes
  }
end

function animateWalk(rig, t)
  
  local s = math.sin(t * WalkSpeed)
  
  rig.leftArmPivot.position =
  vec3( ArmPivotX, ArmPivotY, ArmPivotZ )
  
  rig.rightArmPivot.position =
  vec3(-ArmPivotX, ArmPivotY, ArmPivotZ )
  
  rig.leftLegPivot.position =
  vec3( LegPivotX, LegPivotY, LegPivotZ )
  
  rig.rightLegPivot.position =
  vec3(-LegPivotX, LegPivotY, LegPivotZ )
  
  rig.meshes.leftArm.position =
  -rig.leftArmPivot.position
  
  rig.meshes.rightArm.position =
  -rig.rightArmPivot.position
  
  rig.meshes.leftLeg.position =
  -rig.leftLegPivot.position
  
  rig.meshes.rightLeg.position =
  -rig.rightLegPivot.position
  
  rig.meshes.head.position =
  -rig.headPivot.position
  
  rig.leftArmPivot.rotation =
  quat.eulerAngles( s * ArmSwing, 0, 0)
  
  rig.rightArmPivot.rotation =
  quat.eulerAngles(-s * ArmSwing, 0, 0)
  
  rig.leftLegPivot.rotation =
  quat.eulerAngles(-s * LegSwing, 0, 0)
  
  rig.rightLegPivot.rotation =
  quat.eulerAngles( s * LegSwing, 0, 0)
end

@UberGoober - looks impressive but struggling to get the last two posts to run. Could be down to require command and asset availability/location.

Other thought - flip between V3 and V4 Codea but tend to use V3 generally, V4 when I am looking at Codea changes and trying out the new features- purely down to familiarity.

@sim - is there any way of displaying the version compatibility of code within the editor ? Like - after loading a project scanning it to establish the version then highlight incompatibility before running ?

I have basic templates for both versions.

What’s not working?

I almost exclusively use 3.x btw.

@UberGoober - loaded your last two posts in and ran them both , as is, in V3 and V4 but

wouldn’t run for me - thought it may have been the requires path for some assets.

Will run again and report some of the error codes later – tied up at mo.

There aren’t any unique assets.

Please give me details on what “wouldn’t run” means. Was there an error message?

@UberGoober -

Shuffled things around a little but not changed anything - error in lin 10 below

Line 10 → obv = scene.camera:add(OrbitViewer, vec3(0,8,0), 30, 2, 120)

Documents/WalkCycle/Main.lua:10: bad argument #1 to ‘add’ (Unknown component type)
stack traceback:
[C]: in method ‘add’
Documents/WalkCycle/Main.lua:10: in function ‘setup’

Hope that helps, looks like OrbitViewer not recognised.

I improved it and started a new post for the better version. It does use the Cameras project as a dependency. Let me know if it still has problems.