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

Commit

Permalink
Fix bezier cubic and quadratic bounds, and curve point generation (#683)
Browse files Browse the repository at this point in the history
  • Loading branch information
soywiz authored May 26, 2022
1 parent 7b74117 commit 2eb8bb0
Show file tree
Hide file tree
Showing 12 changed files with 2,131 additions and 1,791 deletions.
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

0 comments on commit 2eb8bb0

Please sign in to comment.