Skip to content
This repository was archived by the owner on Nov 20, 2025. It is now read-only.

Commit 8b5290f

Browse files
Layer compacting
1 parent 9a255e8 commit 8b5290f

2 files changed

Lines changed: 135 additions & 14 deletions

File tree

indigo/indigo/src/main/scala/indigo/shared/platform/SceneProcessor.scala

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import indigo.shared.scenegraph.DirectionLight
1919
import indigo.shared.scenegraph.EntityNode
2020
import indigo.shared.scenegraph.Falloff
2121
import indigo.shared.scenegraph.Graphic
22+
import indigo.shared.scenegraph.Layer
2223
import indigo.shared.scenegraph.LayerEntry
24+
import indigo.shared.scenegraph.LayerKey
2325
import indigo.shared.scenegraph.Light
2426
import indigo.shared.scenegraph.PointLight
2527
import indigo.shared.scenegraph.SceneUpdateFragment
@@ -29,6 +31,7 @@ import indigo.shared.scenegraph.Sprite
2931
import indigo.shared.shader.ShaderData
3032
import indigo.shared.time.GameTime
3133

34+
import scala.annotation.tailrec
3235
import scala.scalajs.js.JSConverters.*
3336

3437
final class SceneProcessor(
@@ -112,16 +115,13 @@ final class SceneProcessor(
112115
}
113116

114117
val displayLayers: scalajs.js.Array[(DisplayLayer, scalajs.js.Array[(String, DisplayObject)])] =
115-
scene.layers
116-
.flatMap(l =>
117-
l.toBatch
118-
.filter(content => content.visible.getOrElse(true))
118+
SceneProcessor
119+
.compactLayers(scene.layers)
120+
.flatMap((maybeLayerKey, contentLayers) =>
121+
contentLayers
119122
.map(
120123
(
121-
l match {
122-
case LayerEntry.Keyed(tag, _) => Some(tag)
123-
case _ => None
124-
},
124+
maybeLayerKey,
125125
_
126126
)
127127
)
@@ -329,6 +329,69 @@ object SceneProcessor {
329329
data = DisplayObjectConversions.packUBO(ub.uniforms, ub.uniformHash, false)
330330
)
331331
}
332+
333+
/** Compact layers by squashing layers that have the same properties.
334+
*/
335+
def compactLayers(layerEntries: Batch[LayerEntry]): Batch[(Option[LayerKey], Batch[Layer.Content])] =
336+
layerEntries.map {
337+
case LayerEntry.NoKey(layer: Layer.Content) =>
338+
val ls = if layer.visible.getOrElse(true) then Batch(layer) else Batch.empty
339+
340+
(None, ls)
341+
342+
case LayerEntry.NoKey(stack: Layer.Stack) =>
343+
val ls = compactContentLayers(stack.toBatch)
344+
345+
(None, ls)
346+
347+
case LayerEntry.Keyed(key, layer: Layer.Content) =>
348+
val ls = if layer.visible.getOrElse(true) then Batch(layer) else Batch.empty
349+
350+
(Option(key), ls)
351+
352+
case LayerEntry.Keyed(key, stack: Layer.Stack) =>
353+
val ls = compactContentLayers(stack.toBatch)
354+
355+
(Option(key), ls)
356+
}
357+
358+
def compactContentLayers(contentLayers: Batch[Layer.Content]): Batch[Layer.Content] =
359+
@tailrec
360+
def rec(remaining: Batch[Layer.Content], current: Layer.Content, acc: Batch[Layer.Content]): Batch[Layer.Content] =
361+
if remaining.length == 0 then acc :+ current
362+
else
363+
val head = remaining.head
364+
val tail = remaining.tail
365+
366+
if head.visible.exists(_ == false) then rec(tail, current, acc)
367+
else if canCompactLayers(current, head) then
368+
rec(
369+
tail,
370+
current.copy(nodes = current.nodes ++ head.nodes, cloneBlanks = current.cloneBlanks ++ head.cloneBlanks),
371+
acc
372+
)
373+
else rec(tail, head, acc :+ current)
374+
375+
val contentLayersFirstIsVisible =
376+
contentLayers.dropWhile(l => l.visible.exists(_ == false))
377+
378+
if contentLayersFirstIsVisible.length < 2 then contentLayersFirstIsVisible
379+
else
380+
val head = contentLayersFirstIsVisible.head
381+
val tail = contentLayersFirstIsVisible.tail
382+
383+
rec(tail, head, Batch.empty)
384+
385+
/** The rule is that if the two layers have all the same properties, ignoring the scene nodes and clone blanks, then
386+
* we can compact them.
387+
*/
388+
def canCompactLayers(a: Layer.Content, b: Layer.Content): Boolean =
389+
a.lights == b.lights &&
390+
a.magnification == b.magnification &&
391+
a.visible == b.visible &&
392+
a.blending == b.blending &&
393+
a.camera == b.camera
394+
332395
}
333396

334397
final case class LightData(

indigo/indigo/src/test/scala/indigo/shared/platform/SceneProcessorTests.scala

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
package indigo.shared.platform
22

3-
import indigo.shared.collections.Batch
4-
import indigo.shared.datatypes.RGBA
5-
import indigo.shared.datatypes.Radians
6-
import indigo.shared.scenegraph.AmbientLight
7-
import indigo.shared.scenegraph.DirectionLight
8-
import indigo.shared.scenegraph.Light
3+
import indigo.*
94

105
class SceneProcessorTests extends munit.FunSuite {
116

@@ -107,7 +102,70 @@ class SceneProcessorTests extends munit.FunSuite {
107102
assertEquals(actual.toList.map(to2dp), expected.toList.map(to2dp))
108103
}
109104

105+
test("Layer compacting") {
106+
val layers: Batch[LayerEntry] =
107+
SceneProcessorTestData.uncompacted
108+
109+
val actual =
110+
SceneProcessor.compactLayers(layers)
111+
112+
val expected =
113+
SceneProcessorTestData.compacted
114+
115+
assertEquals(clue(actual), clue(expected))
116+
}
117+
110118
def to2dp(d: Float): Double =
111119
Math.round(d.toDouble * 100).toDouble / 100.0
112120

113121
}
122+
123+
object SceneProcessorTestData:
124+
125+
val shape: Shape.Box =
126+
Shape.Box(Rectangle(0, 0, 100, 100), Fill.Color(RGBA.Red))
127+
128+
val uncompacted: Batch[LayerEntry] =
129+
Batch(
130+
LayerEntry(Layer.empty),
131+
LayerEntry(LayerKey("b"), Layer.empty),
132+
LayerEntry(
133+
LayerKey("c"),
134+
Layer.Stack(
135+
Layer.Content(shape),
136+
Layer.Content(shape)
137+
)
138+
),
139+
LayerEntry(
140+
LayerKey("d"),
141+
Layer.Stack(
142+
Layer.empty.withCamera(Camera.Fixed(Point.zero)),
143+
Layer.Content(shape).withCamera(Camera.Fixed(Point.zero)),
144+
Layer.Content(shape).withCamera(Camera.Fixed(Point(10))),
145+
Layer.Stack(
146+
Layer(shape).withCamera(Camera.Fixed(Point(10))),
147+
Layer(shape)
148+
)
149+
)
150+
)
151+
)
152+
153+
val compacted: Batch[(Option[LayerKey], Batch[Layer.Content])] =
154+
Batch(
155+
(None, Batch(Layer.Content.empty)),
156+
(Some(LayerKey("b")), Batch(Layer.Content.empty)),
157+
(
158+
Some(LayerKey("c")),
159+
Batch(
160+
Layer.Content(shape, shape)
161+
)
162+
),
163+
(
164+
Some(LayerKey("d")),
165+
Batch(
166+
Layer.Content(shape).withCamera(Camera.Fixed(Point.zero)),
167+
Layer.Content(shape, shape).withCamera(Camera.Fixed(Point(10))),
168+
Layer.Content(shape)
169+
)
170+
)
171+
)

0 commit comments

Comments
 (0)