3

So I got 4 rectangular shapes and I'm trying to apply a sorting algorithm (painters algorithm) to know which shapes (in 3d) I need to draw first and which one after.

Note: The camera is in the bottom right corner:

The correct order would be: purple, red, blue, green (for drawing of course reversed order).

img


So I've implemented an algorithm which creates something like this: Theres every object listed with it's correct successors and predecessors.

ITEM:  red
  predecessor: -
  successor: -
ITEM:  green
  predecessor: -
  successor: red
ITEM:  blue
  predecessor: green
  successor: red
ITEM:  purple
  predecessor: blue, green
  successor: blue, red

How can I sort the items based on the information above to get the correct order? Any help would be really appreciated.

let digraph = {
  red: {
    predecessor: [],
    successor: []
  },
  green: {
    predecessor: [],
    successor: ["red"]
  },
  blue: {
    predecessor: ["green"],
    successor: ["red"]
  },
  purple: {
    predecessor: ["blue", "green"],
    successor: ["blue", "red"]
  }
}

let itinerary = {}
for (let e of Object.keys(digraph)) {
  if (digraph[e].successor.length != 0) itinerary[e] = digraph[e]
}

//console.log(itinerary)
let sorted_pile = []
let overflow = 0;
while (Object.keys(itinerary).length) {
  overflow++;
  if (overflow > 40) {
    console.error("OVERFLOW");
    break;
  }
  let key = Object.keys(itinerary)[0],
    entity = itinerary[key];
  delete itinerary[key];
  sorted_pile.push(key)
  let successors = digraph[key].successor
  for (succ of successors) {
    digraph[succ].predecessor = digraph[succ].predecessor.filter(function(item) {
      return item !== key;
    })
    if (digraph[succ].predecessor.length === 0) itinerary[key] = digraph[succ]
  }
}

console.log(sorted_pile)

Edit:

let tile_entities = [
    {x: 8, y: 0, w: 1, h: 5, id: "rot"},
    {x: 5, y: 0, w: 2, h: 1, id: "gruen"},
    {x: 7, y: 0, w: 1, h: 1, id: "blau"},
    {x: 4, y: 5, w: 4, h: 2, id: "lila"},
]
  • "*every object is listed with it's correct successors and predecessors*" - why so complicated? Why not just sort by distance from the camera? – Bergi Jan 28 '19 at 18:50
  • Hey, Bergi. Thanks for your answer - unfortunately this is not as easy as you might think (in your defense: calculating the camera distance was also my first approach). The problem is that the objects encounter the problem of the painter algorithm by their w/h. If you're interested, take a look at this source, in my opinion it makes a lot of things clear and has helped me as well: 1: [cs.umd](http://www.cs.umd.edu/~djacobs/CMSC427/VisibilityNonDiscrete.pdf) 2: [siggraph](https://www.siggraph.org//education/materials/HyperGraph/scanline/visibility/painter.htm) Best regards - Jonas –  Jan 28 '19 at 18:55
  • 1
    In your example, purple has blue as both predecessor and successor. What does that mean? Both conditions cannot be true at the same time... – trincot Jan 28 '19 at 18:59
  • oh yeah @trincot - the conditions **can** be both true. I guess this depends on the size and position of the objects and can unfortunately no be avoided. –  Jan 28 '19 at 19:01
  • So does that mean that the *absence* of an information, means that it is an impossibility? For instance, there is no indication for red that it *can* have successors, but yet in the solution, it has. If also this is not true, then what *information* is there in this data structure? I cannot find anything useful then. – trincot Jan 28 '19 at 19:05
  • @trincot _So does that mean that the absence of an information, means that it is an impossibility?_ - Yeah you are right. Probably this is a problem! **It is quite possible that my problem is already with the data collection** (finding the predecessor/successor). How would you find out if an element has a predecessor or successor? –  Jan 28 '19 at 19:08
  • Yes, there is problem with your data collection. As best I can interpret this, the data in your digraph is inconsistent with the 2D picture you provide. – Prune Jan 28 '19 at 19:09
  • I would expect that the "reverse painter's" algorithm would fit your needs, but my uncertainty about the problem set-up keeps me from making such a simplistic conclusion. – Prune Jan 28 '19 at 19:10
  • Are you already using a "topological sort"? This doesn't work for a graph with cycles, but we may need to identify a way to work around that. For instance, split a "schizophrenic" region into two contiguous blocks, one on each end of the cycle. The end result desired would be to have Purple1 < Blue < Purple2. – Prune Jan 28 '19 at 19:12
  • Yes of course, my code is the implementation of a topological sorting. However, I must confess that I don't really know what you mean by dividing the schizophrenic regions (what do you mean by these regions anyway?). @Prune –  Jan 28 '19 at 19:14
  • My own term -- I hoped it would be self-explanatory. I refer to those regions that are in a cycle. If we break one such region into two independent regions, and let those be the ends of a path, we can break the cycle. The problem then reduces to partitioning the region such that the two parts don't overlap from the camera's viewpoint. – Prune Jan 28 '19 at 19:17
  • Sounds like a pretty interesting approach - you're right, the topological sorting I used still doesn't seem to solve my problem. I'm sitting on it but I'm not really sure if it's feasible for me. Thanks. @Prune –  Jan 28 '19 at 19:23
  • That Wikipedia article contains [an example](https://en.wikipedia.org/wiki/Painter%27s_algorithm#/media/File:Painters_problem.svg) of the sorts of things you need to solve before trying a topological sort. There's no way around needing to do this. – Scott Sauyet Jan 28 '19 at 19:27
  • Yeah this is what will happen if I got a 3d intersection of two objects - but this will not happen in my case - objects will not intersect in 3d space. @ScottSauyet –  Jan 28 '19 at 19:28
  • Well somehow you got the `blue < purple < blue` problem. If you know that you can't have that and can fix the generation of this, then it's just a matter of choosing your topological sort technique. – Scott Sauyet Jan 28 '19 at 19:30
  • Sorry that I ask so stupidly - but it seems I made a mistake in the data collection. How do I find out if an element is a predecessor? Suppose I have these coordinates: `let tile_entities = [ {x: 8, y: 0, w: 1, h: 5, id: "red"}, {x: 5, y: 0, w: 2, h: 1, id: "green"}, {x: 7, y: 0, w: 1, h: 1, id: "blue"}, {x: 4, y: 5, w: 4, h: 2, id: "purple"}, ]` –  Jan 28 '19 at 20:18

1 Answers1

0

I think this does what you want, but I start with an altered version of your input. It's easy enough to transform it, but you might be able to generate this input directly from your current process. (Note that the successor information is not needed, since it's derivable from the predecessors)

const isEmpty = arr => arr.length == 0 
const removeIndex = (n, arr) => arr.slice(0, n).concat(arr.slice(n + 1))

const sortDigraph = (
  digraph, 
  sorted = [], 
  idx = digraph.findIndex(node => isEmpty(node.predecessor)),
  nodeName = (digraph[idx] || {}).name
) => isEmpty(digraph)
  ? sorted
  : sortDigraph(
    removeIndex(idx, digraph).map(({name, predecessor}) => ({
      name,
      predecessor: predecessor.filter(n => n !== nodeName)
    }), digraph),
    sorted.concat(nodeName)
  )
  
let digraph = [
  {name: 'blue', predecessor: ["green"]},
  {name: 'green', predecessor: []},
  {name: 'orange', predecessor: ["green"]},
  {name: 'purple', predecessor: ["blue", "green"]},
  {name: 'red', predecessor: ["yellow"]},
  {name: 'yellow', predecessor: ["green", "orange"]},
]

console.log(sortDigraph(digraph))

The basic idea is that you can start with any one that has no predecessors, and then strike out any predecessor links to it from the other ones, and repeat the process. So long as your graph is acyclic, this should simply work. If you have to deal with the more complicated case of the Painter's Algorithm, then you will need to break apart your nodes before trying this.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • Thanks a lot for your answer I'll have a look at your code later - but it currently seems that changing the order of the digraph will mix up the complete order. Greetings- jonas –  Jan 28 '19 at 19:21
  • 1
    I wrote this before seeing the discussion in the comments, and without noting the `blue < purple < blue` condition in your example. This clearly won't work for that. And you cannot get a topological sort in such a case. – Scott Sauyet Jan 28 '19 at 19:22
  • @jonas00: It can vary with the order. It will find an order that works, not a unique one. There is not generally a unique total order that matches your partial order. We could make it more complex and find, say, the alphabetically first one that works at each stage. I don't know how important that would be. – Scott Sauyet Jan 28 '19 at 19:24
  • The way I see it, it's still an important part - if the sorting changes (each time) according to the order, you're sure to get unattractive renderBugs. Don't you think? –  Jan 28 '19 at 19:26
  • I don't know much about this sort of rendering, but if you look at the wikipedia initial example, does it really matter *which* tree is painted first, so long as they're all painted after the meadows, which are painted after the mountain? If so, it's not hard to use a secondary sort (alphabetic?) instead of the first match. In fact, if you sort these by name first, you should be consistent. – Scott Sauyet Jan 28 '19 at 19:35
  • Hey Scott. Would you mind to add an simple algorithm how to get the data for the digraph? Tbh I'm not sure where my implementation fails. But obviously it does. Best regards- jonas :) (**Note** : I've added the tile entities to the question) –  Jan 31 '19 at 16:18
  • If you just want to convert your digraph format to the one I used, this should do it: `const newDigraph = Object.keys(digraph).map(name => ({name, predecessor: digraph[name].predecessor}))`. But if you're looking to do it from your graphics system, I'm afraid that would require much more knowledge of your system than I have. I'm guessing, though, that if you can create your current format, you could just as easily generate this one. – Scott Sauyet Jan 31 '19 at 17:29