Skip to content

Instantly share code, notes, and snippets.

@jeiting
Created April 18, 2013 20:48
Show Gist options
  • Select an option

  • Save jeiting/5416096 to your computer and use it in GitHub Desktop.

Select an option

Save jeiting/5416096 to your computer and use it in GitHub Desktop.
-- Created by Alexey Chernikov on Feb 19, 2013
require ("Mixin")
Group = {}
------------------------------------------------------------ BOUNDS LOGIC ----------------------------------------------
-- Moai propagates transformations with parent-child relationships, but it does not calculate bounds based on those transformations
-- This extends moai to update bounds for a hierarchy of transformed scene objects
---------------------------------------------------------- SORT LOGIC --------------------------------------------
-- Moai has support for primitive sort modes, but no general mechanism for sorting added/removed complex elements.
-- Use Moai's support for sorting based on element priority, but update priorities in a hierarchy of nodes.
-- Keep a tree that increases indices from bottom left, depth-first, with the root having the max index
-- In such a tree, we need two resorting operations: sorting the children of a certain node, and sorting it's parents
-- When sorting the children of a node, the smallest possible index goes to the bottom left node. The smallest index is
-- defined externally (e.g. when inserting a subtree, the parent node's current sort index is the smallest index)
-- When sorting the parents of a node, all the children to the right of a node which recently changed its structure must
-- be relabeled. The labeling for a parent node is the maximum label of it's children + 1 (i.e. rightmost deepest element + 1)
-- So to complete a full resort on a node whose structure has changed, first relabel its children, then apply a new label to
-- the node. This changes the structure of its parent node, and the change must propagate upwards and rightwards.
local function sortChildren(self, localIndex, sortIndex)
if(self.children) then
for i = localIndex, #self.children do
local child = self.children[i]
if(child.children) then
sortIndex = sortChildren(child, 1, sortIndex)
end
child.zIndex = i
child.sortIndex = sortIndex
child:setPriority(child.sortIndex)
sortIndex = sortIndex + 1
end
end
self.sortIndex = sortIndex
self:setPriority(sortIndex)
return sortIndex
end
local function sortParents(self)
-- We don't need to sort all the children below the current node
if(self.parent == nil) then
return
end
local startIndex = self.zIndex + 1
local curSortIndex = self.sortIndex + 1
sortChildren(self.parent, startIndex, curSortIndex)
sortParents(self.parent)
end
local function resortInsert(self, insertedChildIndex)
sortChildren(self, insertedChildIndex, self.sortIndex)
sortParents(self)
end
--------------------------- Sort remove procedure, it's a little bit more involved than the insertion procedure with the selected labeling scheeme -----------------------------
-- When removing a node, define the Minimum Sort Index as the index of the deepest, leftmost node in the removed subtree (it is the starting sort index for updating the tree's structure)
-- Removing has several cases:
-- 1) Remove an element that has neighbours to the left and to the right. The Minimum Sort Index is the sort index of the left neighbour, +1
-- 2) Remove an element that has neighbours to the right, but not to the left. The Minimum Sort Index must be fetched from the bottom of the removed subtree,
-- or it is the index of the closest ancestor that has a left node, +1 (if no ancestors have a left node, then the Minimum Sort Index is 1)
-- 3) Remove an element that has neighbours only on the left side. Special case of 1, no special treatment required
-- For resolving 2, this implementation chooses to traverse the removed node as a subtree, rather than the parent structure
local function findMinSortIndexInSubtree(root)
local children = root.children
if(children == nil) then return self.sortIndex; end
local leftMostNode = children[1]
if(leftMostNode == nil) then
return root.sortIndex
elseif(leftMostNode.children) then
return findMinSortIndexInSubtree(leftMostNode)
else
return leftMostNode.sortIndex
end
end
local function findMinSortIndexForRemovedChild(self, removedChild)
local children = self.children
local minSortIndex = removedChild.sortIndex
local removedChildIndex = removedChild.zIndex
if(removedChildIndex > 1) then -- Has neighbours to the left (cases 1, 3)
local prevChild = children[removedChildIndex - 1]
minSortIndex = prevChild.sortIndex + 1
else
minSortIndex = findMinSortIndexInSubtree(removedChild)
end
return minSortIndex
end
-- Assumes the node was already removed from the hierarchy that needs to be updated
local function resortRemove(self, removedChild)
local minSortIndex = findMinSortIndexForRemovedChild(self, removedChild)
sortChildren(self, removedChild.zIndex, minSortIndex)
sortParents(self)
end
------------------------------------------------------- MOAI WRAP LOGIC ----------------------------------------------
-- Extend the basic prop with functionality for hierarchically storing children
-- This implies bounds checking and sorting
local GroupFunctions =
{
insert = function(self, sceneObject, index)
if(sceneObject.parent) then
sceneObject.parent:remove(sceneObject)
end
index = index or #self.children + 1
if(index > #self.children + 1) then index = #self.children + 1 end
table.insert(self.children, index, sceneObject)
local xMin, yMin, zMin, xMax, yMax, zMax = sceneObject:getWorldBounds()
xMin, yMin, zMin, xMax, yMax, zMax = sceneObject:getBounds()
sceneObject:setParent(self)
sceneObject.parent = self
resortInsert(self, index)
end,
-- This is an optimized implementation of the function
-- It's tangled with the process of finding the smallest subindex in the cleared node (necessary for updating sort indices any time something is removed),
-- which in a general untangled case would have to be done as a separate (recursive) step, or as a result of a child-by-child removal of subnodes
removeAll = function(self)
local newSortIndex = self.sortIndex
if(#self.children > 0) then
newSortIndex = self.children[1].sortIndex -- leftmost leaf child always has the smallest sort index (ensure this is the leaf recursively)
end
for i, child in ipairs(self.children) do
if(child.removeAll) then
local childSortIndex = child:removeAll()
if(childSortIndex < newSortIndex) then
newSortIndex = childSortIndex
end
end
removeFromScreen(child)
end
self.children = {}
self.sortIndex = newSortIndex
return newSortIndex
end,
removeSelf = function(self)
self:removeAll() -- avoid removing child by child, but still need to remove all the subtrees down to the leaves from the render and update pipelines
self.parent:remove(self)
end,
remove = function(self, childToRemove)
if(childToRemove.parent ~= self) then
return
end
removeFromScreen(childToRemove)
removeFromChildrenList(childToRemove, self.children)
resortRemove(self, childToRemove)
childToRemove.parent = nil
end,
calculateBounds = function(self)
local xMin, yMin, xMax, yMax = math.huge, math.huge, -math.huge, -math.huge
local cxMin, cxMax, cyMin, cyMax, czMin, czMax
for i, child in ipairs(self.children) do
if(child.calculateBounds) then
local childBounds = child:calculateBounds()
cxMin, cxMax, cyMin, cyMax = childBounds.xMin, childBounds.xMax, childBounds.yMin, childBounds.yMax
else
cxMin, cyMin, czMin, cxMax, cyMax, czMax = child:getBounds()
end
cxMin, cyMin, cxMax, cyMax = cxMin or 0, cyMin or 0, cxMax or 0, cyMax or 0
local x, y = child:getLoc()
local scale = child:getScl()
local w, h = cxMax - cxMin, cyMax - cyMin
cxMin = cxMin + x
cxMax = cxMin + w * scale
cyMin = cyMin + y
cyMax = cyMin + h * scale
if(cxMin < xMin) then xMin = cxMin end
if(cyMin < yMin) then yMin = cyMin end
if(cxMax > xMax) then xMax = cxMax end
if(cyMax > yMax) then yMax = cyMax end
end
self:setBounds(xMin, yMin, 0, xMax, yMax, 0)
return {xMin = xMin, yMin = yMin, xMax = xMax, yMax = yMax, w = xMax - xMin, h = yMax - yMin}
end,
getBoundsTable = function(self)
local xMin, yMin, zMin, xMax, yMax, zMax = self:getBounds()
return {xMin = xMin, yMin = yMin, xMax = xMax, yMax = yMax, w = xMax - xMin, h = yMax - yMin}
end,
printTree = function(self, indent)
local indent = indent or "-"
if(indent == "-") then
print("Root:", self.sortIndex)
end
for index, child in ipairs(self.children) do
print(indent..child.sortIndex)
if(child.printTree) then
child:printTree(indent.."-")
end
end
end
}
function Group.ify(prop)
Mixin.mix(prop, GroupFunctions)
prop.children = {}
prop.sortIndex = 0
prop.zIndex = 0
end
function Group.new()
local self = MOAIProp2D.new()
self:setBounds(0, 0, 0, 0, 0, 0)
return self
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment