Skip to content
This repository has been archived by the owner on Jul 8, 2022. It is now read-only.

Fix bezier cubic and quadratic bounds #683

Merged
merged 5 commits into from
May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions korge-sandbox/src/commonMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ suspend fun main() = Korge(
multithreaded = true,
//debugAg = true,
) {
mainClipping()
mainBezier()
//mainGpuVectorRendering()
//mainClipping()
//mainTextureIssue()
//mainTilemapTest()
//mainTransition()
Expand All @@ -45,7 +47,6 @@ suspend fun main() = Korge(
//mainSkybox()
//mainHaptic()
//mainMasks()
//mainGpuVectorRendering()
//mainColorPicker()
//mainSvgAnimation()
//mainFiltersRenderToBitmap()
Expand Down
127 changes: 127 additions & 0 deletions korge-sandbox/src/commonMain/kotlin/MainBezier.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import com.soywiz.klock.seconds
import com.soywiz.korge.tween.get
import com.soywiz.korge.tween.tween
import com.soywiz.korge.view.Graphics
import com.soywiz.korge.view.Stage
import com.soywiz.korge.view.addUpdater
import com.soywiz.korge.view.graphics
import com.soywiz.korge.view.vector.gpuShapeView
import com.soywiz.korim.color.Colors
import com.soywiz.korim.vector.EmptyShape
import com.soywiz.korim.vector.StrokeInfo
import com.soywiz.korio.async.launch
import com.soywiz.korma.geom.Point
import com.soywiz.korma.geom.Rectangle
import com.soywiz.korma.geom.bezier.Bezier
import com.soywiz.korma.geom.vector.circle
import com.soywiz.korma.geom.vector.cubic
import com.soywiz.korma.geom.vector.curve
import com.soywiz.korma.geom.vector.lineTo
import com.soywiz.korma.geom.vector.moveTo
import com.soywiz.korma.geom.vector.rect
import com.soywiz.korma.random.get
import kotlin.random.Random

suspend fun Stage.mainBezier() {
val shape = gpuShapeView(EmptyShape)
fun getRandomPoint() = Point(Random[100..500], Random[100..500])
class Bez {
var p1 = getRandomPoint()
var p2 = getRandomPoint()
var p3 = getRandomPoint()
var p4 = getRandomPoint()
}
val bez = Bez()

addUpdater {
shape.updateShape {
//val curve = Bezier.Quad(bez.p1, bez.p2, bez.p3)
val curve = Bezier.Cubic(bez.p1, bez.p2, bez.p3, bez.p4)

stroke(Colors.WHITE, lineWidth = 4.0) {
beginPath()
curve(curve)
}

stroke(Colors.DIMGREY, lineWidth = 1.6) {
moveTo(bez.p1)
lineTo(bez.p2)
lineTo(bez.p3)
lineTo(bez.p4)
}
stroke(Colors.PURPLE, lineWidth = 2.0) {
for (n in 0..50) {
val p = curve.calc(n.toDouble() / 50.0)
this.circle(p, 1.0)
}
}

//stroke(Colors.YELLOW, lineWidth = 2.0) {
fill(Colors.YELLOW) {
this.circle(bez.p1, 8.0)
this.circle(bez.p2, 4.0)
this.circle(bez.p1, 4.0)
this.circle(bez.p3, 4.0)
this.circle(bez.p4, 4.0)
}

//fill(Colors.YELLOW) { this.circle(bez.p1, 8.0) }
//fill(Colors.YELLOW) { this.circle(bez.p2, 4.0) }
//fill(Colors.YELLOW) { this.circle(bez.p1, 4.0) }
//fill(Colors.YELLOW) { this.circle(bez.p3, 4.0) }
//fill(Colors.YELLOW) { this.circle(bez.p4, 4.0) }

stroke(Colors.RED, lineWidth = 2.0) {
rect(curve.getBounds())
}
}
}

launch {
while (true) {
tween(
bez::p1[getRandomPoint()],
bez::p2[getRandomPoint()],
bez::p3[getRandomPoint()],
bez::p4[getRandomPoint()],
time = 1.seconds
)
}
}

/*
run {
val p0 = Point(109, 135)
val p1 = Point(25, 190)
val p2 = Point(210, 250)
val p3 = Point(234, 49)
val g = graphics()
g.clear()
g.stroke(Colors.DIMGREY, info = StrokeInfo(thickness = 1.0)) {
moveTo(p0)
lineTo(p1)
lineTo(p2)
lineTo(p3)
}
g.stroke(Colors.WHITE, info = StrokeInfo(thickness = 2.0)) {
cubic(p0, p1, p2, p3)
}
val ratio = 0.3
val cubic2 = Bezier.Cubic().setToSplitFirst(Bezier.Cubic(p0, p1, p2, p3), ratio)
val cubic3 = Bezier.Cubic().setToSplitSecond(Bezier.Cubic(p0, p1, p2, p3), ratio)

g.stroke(Colors.PURPLE, info = StrokeInfo(thickness = 4.0)) {
cubic(cubic2)
}
g.stroke(Colors.YELLOW, info = StrokeInfo(thickness = 4.0)) {
cubic(cubic3)
}
graphics {
stroke(Colors.RED, StrokeInfo(thickness = 2.0)) {
rect(g.getLocalBounds())
}
}
}
return
*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.soywiz.korge.view.vector
import com.soywiz.kds.FastIdentityMap
import com.soywiz.kds.clear
import com.soywiz.kds.getOrPut
import com.soywiz.kds.iterators.fastForEach
import com.soywiz.klock.measureTime
import com.soywiz.klogger.Console
import com.soywiz.kmem.clamp
Expand Down Expand Up @@ -45,6 +46,8 @@ import com.soywiz.korma.geom.expand
import com.soywiz.korma.geom.minus
import com.soywiz.korma.geom.plus
import com.soywiz.korma.geom.shape.emitPoints2
import com.soywiz.korma.geom.shape.getPoints2
import com.soywiz.korma.geom.shape.getPoints2List
import com.soywiz.korma.geom.shape.toPathPointList
import com.soywiz.korma.geom.vector.LineCap
import com.soywiz.korma.geom.vector.LineJoin
Expand All @@ -63,7 +66,7 @@ inline fun Container.gpuShapeView(

@KorgeExperimental
inline fun Container.gpuShapeView(
shape: Shape,
shape: Shape = EmptyShape,
antialiased: Boolean = true,
callback: @ViewDslMarker GpuShapeView.() -> Unit = {}
) =
Expand All @@ -79,6 +82,7 @@ open class GpuShapeView(
var autoScaling: Boolean = true
) : View(), Anchorable {
private val pointCache = FastIdentityMap<VectorPath, PointArrayList>()
private val pointListCache = FastIdentityMap<VectorPath, List<PointArrayList>>()
private val gpuShapeViewCommands = GpuShapeViewCommands()
private val bb = BoundsBuilder()
var bufferWidth = 1000
Expand Down Expand Up @@ -111,6 +115,7 @@ open class GpuShapeView(
private fun invalidateShape() {
renderCount = 0
pointCache.clear()
pointListCache.clear()

gpuShapeViewCommands.clear()
gpuShapeViewCommands.clearStencil()
Expand Down Expand Up @@ -504,12 +509,10 @@ open class GpuShapeView(
}
//private var lastPointsString = ""

class PointsResult(val bounds: Rectangle, val vertexCount: Int)
class PointsResult(val bounds: Rectangle, val vertexCount: Int, val vertexStart: Int, val vertexEnd: Int)

private fun getPointsForPath(path: VectorPath): PointsResult {
val points: PointArrayList = pointCache.getOrPut(path) {
PointArrayList().also { points -> path.emitPoints2 { x, y, move -> points.add(x, y) } }
}
private fun getPointsForPath(points: PointArrayList): PointsResult {
val vertexStart = gpuShapeViewCommands.verticesStart()
val bb = BoundsBuilder()
bb.reset()
val startIndex = gpuShapeViewCommands.addVertex(0f, 0f)
Expand All @@ -520,7 +523,16 @@ open class GpuShapeView(
bb.add(x, y)
}
gpuShapeViewCommands.updateVertex(startIndex, ((bb.xmax + bb.xmin) / 2).toFloat(), ((bb.ymax + bb.ymin) / 2).toFloat())
return PointsResult(bb.getBounds(), points.size + 2)
val vertexEnd = gpuShapeViewCommands.verticesEnd()
return PointsResult(bb.getBounds(), points.size + 2, vertexStart, vertexEnd)
}

private fun getPointsForPath(path: VectorPath): PointsResult {
return getPointsForPath(pointCache.getOrPut(path) { path.getPoints2() })
}

private fun getPointsForPathList(path: VectorPath): List<PointsResult> {
return pointListCache.getOrPut(path) { path.getPoints2List() }.map { getPointsForPath(it) }
}

var maxRenderCount: Int = 100_000
Expand Down Expand Up @@ -553,20 +565,21 @@ open class GpuShapeView(
lineWidth = 10000000.0,
) ?: return

val pathDataStart = gpuShapeViewCommands.verticesStart()
val pathData = getPointsForPath(shape.path)
val pathDataEnd = gpuShapeViewCommands.verticesEnd()
val pathBounds = pathData.bounds.clone().expand(2, 2, 2, 2)
val pathDataList = getPointsForPathList(shape.path)
val pathBoundsNoExpended = BoundsBuilder().also { bb -> pathDataList.fastForEach { bb.add(it.bounds) } }.getBounds()
val pathBounds = pathBoundsNoExpended.clone().expand(2, 2, 2, 2)

if (!shape.requireStencil && shape.clip == null) {
gpuShapeViewCommands.draw(
AG.DrawType.TRIANGLE_FAN,
startIndex = pathDataStart,
endIndex = pathDataEnd,
paintShader = paintShader,
colorMask = AG.ColorMaskState(true),
blendMode = BlendMode.NONE.factors,
)
pathDataList.fastForEach { pathData ->
gpuShapeViewCommands.draw(
AG.DrawType.TRIANGLE_FAN,
startIndex = pathData.vertexStart,
endIndex = pathData.vertexEnd,
paintShader = paintShader,
colorMask = AG.ColorMaskState(true),
blendMode = BlendMode.NONE.factors,
)
}
return
}

Expand All @@ -582,12 +595,16 @@ open class GpuShapeView(
//gpuShapeViewCommands.clearStencil(0)

var stencilEqualsValue = 0b00000001
writeStencil(pathDataStart, pathDataEnd, AG.StencilState(
enabled = true,
compareMode = AG.CompareMode.ALWAYS,
writeMask = 0b00000001,
actionOnBothPass = AG.StencilOp.INVERT,
))
pathDataList.fastForEach { pathData ->
writeStencil(
pathData.vertexStart, pathData.vertexEnd, AG.StencilState(
enabled = true,
compareMode = AG.CompareMode.ALWAYS,
writeMask = 0b00000001,
actionOnBothPass = AG.StencilOp.INVERT,
)
)
}

// @TODO: Should we do clipping other way?
if (clipData != null) {
Expand Down Expand Up @@ -627,7 +644,7 @@ open class GpuShapeView(
)
}

writeFill(paintShader, stencilEqualsValue, pathBounds, pathDataStart, pathDataEnd)
writeFill(paintShader, stencilEqualsValue, pathBounds, pathDataList)

gpuShapeViewCommands.clearStencil(0)

Expand All @@ -650,8 +667,7 @@ open class GpuShapeView(
paintShader: GpuShapeViewPrograms.PaintShader,
stencilEqualsValue: Int,
pathBounds: Rectangle,
pathDataStart: Int,
pathDataEnd: Int
pathDataList: List<PointsResult>,
) {
val vstart = gpuShapeViewCommands.verticesStart()
val x0 = pathBounds.left.toFloat()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class GraphicsTest {
cubic(cubic3)
}
}
assertEquals(Rectangle(0, 0, 234, 250), g.getLocalBounds())
assertEquals(Rectangle(25, 49, 209, 201), g.getLocalBounds())
}
}

Expand Down
Loading