Created
April 18, 2013 20:48
-
-
Save jeiting/5416096 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- 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