实现等高线的绘制

This commit is contained in:
2025-11-25 19:43:51 +08:00
parent de15029b2b
commit 816e954ed8
31 changed files with 3553 additions and 257 deletions

View File

@@ -30,6 +30,7 @@ kotlin {
implementation(project(":orx-marching-squares"))
implementation(project(":orx-text-writer"))
implementation(project(":orx-obj-loader"))
implementation(project(":orx-palette"))
}
}
}

View File

@@ -0,0 +1,35 @@
import org.openrndr.application
import org.openrndr.shape.Rectangle
/**
* Demonstrates how to use a ColorBrewer2 palette.
* Finds the first available palette with 5 colors,
* then draws concentric circles filled with those colors.
*/
fun main() = application {
configure {
width = 720
height = 720
}
program {
val palette = colorBrewer2Palettes(
numberOfColors = 6,
paletteType = ColorBrewer2Type.Any
).first().colors
val cellSize = 50.0
extend {
palette.forEachIndexed { i, color ->
drawer.fill = color
drawer.rectangle(
Rectangle(
x = 0.0,
y = cellSize * i,
width = cellSize,
height = cellSize
)
)
// drawer.circle(drawer.bounds.center, 300.0 - i * 40.0)
}
}
}
}

View File

@@ -10,7 +10,6 @@ import org.openrndr.draw.loadFont
import org.openrndr.extra.camera.Camera2D
import org.openrndr.extra.marchingsquares.findContours
import org.openrndr.extra.noise.gradientPerturbFractal
import org.openrndr.extra.noise.simplex
import org.openrndr.extra.textwriter.writer
import org.openrndr.extra.triangulation.DelaunayTriangulation
import org.openrndr.math.Vector2
@@ -18,7 +17,6 @@ import org.openrndr.math.Vector3
import org.openrndr.shape.Segment2D
import org.openrndr.shape.Segment3D
import org.openrndr.shape.ShapeContour
import kotlin.math.absoluteValue
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
@@ -30,7 +28,7 @@ import kotlin.random.Random
fun main() = application {
configure {
width = 720
height = 720
height = 480
title = "Delaunator"
}
program {
@@ -62,6 +60,10 @@ fun main() = application {
extend(Camera2D())
println("draw")
var targetHeight: Double = zs.average()
val step = zs.max() - zs.min() / 6
var heightList = (0..5).map { index ->
zs.min() + step * index
}
var logEnabled = true
var useInterpolation = false
var sampleLinear = false
@@ -171,12 +173,41 @@ fun main() = application {
cellSize = 4.0,
useInterpolation = useInterpolation
)
val associateWith: List<List<ShapeContour>> = heightList.map { height ->
findContours(
f = {
val triangle = triangles.firstOrNull { triangle ->
isPointInTriangle(it, listOf(triangle.x1, triangle.x2, triangle.x3))
}
triangle ?: return@findContours 0.0
val interpolate = interpolateHeight(
point = it,
triangle = listOf(
triangle.x1,
triangle.x2,
triangle.x3,
).map {
Vector3(it.x, it.y, associate[it]!!)
}
)
interpolate.z - height
},
area = drawer.bounds,
cellSize = 4.0,
useInterpolation = useInterpolation
)
}
if (logEnabled) println("useInterpolation = $useInterpolation")
drawer.stroke = null
contours.forEach {
if (true) contours.forEach {
drawer.fill = ColorRGBa.GREEN.opacify(0.1)
drawer.contour(if (sampleLinear) it.sampleLinear() else it)
}
if (false) associateWith.forEachIndexed { index, contours ->
contours.forEach {
drawer.fill = colorBrewer2[index].colors.first().opacify(0.1)
drawer.contour(it)
}
}
drawer.fontMap = loadFont("demo-data/fonts/IBMPlexMono-Regular.ttf", 24.0)
@@ -210,6 +241,24 @@ fun isPointInTriangle(point: Vector2, triangle: List<Vector2>): Boolean {
alpha <= 1 && beta <= 1 && gamma <= 1
}
fun isPointInTriangle3D(point: Vector2, triangle: List<Vector3>): Boolean {
require(triangle.size == 3) { "三角形必须有3个顶点" }
val (v1, v2, v3) = triangle
// 计算重心坐标
val denominator = (v2.y - v3.y) * (v1.x - v3.x) + (v3.x - v2.x) * (v1.y - v3.y)
if (denominator == 0.0) return false // 退化三角形
val alpha = ((v2.y - v3.y) * (point.x - v3.x) + (v3.x - v2.x) * (point.y - v3.y)) / denominator
val beta = ((v3.y - v1.y) * (point.x - v3.x) + (v1.x - v3.x) * (point.y - v3.y)) / denominator
val gamma = 1.0 - alpha - beta
// 点在三角形内当且仅当所有重心坐标都在[0,1]范围内
return alpha >= 0 && beta >= 0 && gamma >= 0 &&
alpha <= 1 && beta <= 1 && gamma <= 1
}
/**
* 使用重心坐标计算点在三角形上的高度
* @param point 二维点 (x, y)

View File

@@ -9,15 +9,14 @@ import org.openrndr.draw.loadFont
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.marchingsquares.findContours
import org.openrndr.extra.noise.gradientPerturbFractal
import org.openrndr.extra.objloader.loadOBJasVertexBuffer
import org.openrndr.extra.textwriter.writer
import org.openrndr.extra.triangulation.DelaunayTriangulation
import org.openrndr.extra.triangulation.DelaunayTriangulation3D
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.shape.Path3D
import org.openrndr.shape.Segment3D
import org.openrndr.shape.ShapeContour
/**
* @author tabidachinokaze
@@ -48,15 +47,16 @@ fun main() = application {
height = height,
volcanoCount = 3
)*/
val points3D = coordinateGenerate(width, height).map {
it.copy(x = it.x - width / 2, y = it.y - height / 2)
}
val points3D = coordinateGenerate(width, height).map {
it.copy(x = it.x - width / 2, y = it.y - height / 2, z = it.z * 10)
}
val zs = points3D.map { it.z }
println("zs = ${zs}")
val associate: MutableMap<Vector2, Double> = points3D.associate {
Vector2(it.x, it.y) to it.z
}.toMutableMap()
val delaunay = DelaunayTriangulation(associate.map { it.key })
val delaunay3D = DelaunayTriangulation3D(points3D.map { Vector3(it.x, it.y, it.z) })
//println(points3D.niceStr())
//extend(Camera2D())
@@ -84,7 +84,7 @@ fun main() = application {
val vb = loadOBJasVertexBuffer("orx-obj-loader/test-data/non-planar.obj")
extend {
val triangles = delaunay.triangles()
val triangles = delaunay3D.triangles()
val segments = mutableListOf<Segment3D>()
drawer.clear(ColorRGBa.BLACK)
val indexDiff = (frameCount / 1000) % triangles.size
@@ -95,10 +95,12 @@ fun main() = application {
}
drawer.vertexBuffer(vb, DrawPrimitive.TRIANGLES)
// 绘制等高线段区域
for ((i, triangle) in triangles.withIndex()) {
val segment2DS = triangle.contour.segments.filter {
val startZ = associate[it.start]!!
val endZ = associate[it.end]!!
val segment2DS = triangle.segments.filter {
val startZ = it.start.z
val endZ = it.end.z
if (startZ < endZ) {
targetHeight in startZ..endZ
} else {
@@ -108,8 +110,8 @@ fun main() = application {
if (segment2DS.size == 2) {
val vector2s = segment2DS.map {
val startZ = associate[it.start]!!
val endZ = associate[it.end]!!
val startZ = it.start.z
val endZ = it.end.z
val start = Vector3(it.start.x, it.start.y, startZ)
val end = Vector3(it.end.x, it.end.y, endZ)
if (startZ < endZ) {
@@ -130,14 +132,8 @@ fun main() = application {
}
drawer.strokeWeight = 20.0
drawer.stroke = ColorRGBa.PINK
val segment3DS = triangle.contour.segments.map {
val startZ = associate[it.start]!!
val endZ = associate[it.end]!!
Segment3D(it.start.vector3(z = startZ), it.end.vector3(z = endZ))
}
//drawer.contour(triangle.contour)
drawer.path(Path3D.fromSegments(segment3DS, closed = true))
drawer.path(triangle.path)
}
val sorted = connectAllSegments(segments)
@@ -161,6 +157,7 @@ fun main() = application {
drawer.fill = ColorRGBa.YELLOW
// if (false) drawer.contour(ShapeContour.fromSegments(it, closed = true))
}
// 结束绘制等高线
/*for (y in 0 until (area.height / cellSize).toInt()) {
for (x in 0 until (area.width / cellSize).toInt()) {
values[IntVector2(x, y)] = f(Vector2(x * cellSize + area.x, y * cellSize + area.y))
@@ -169,7 +166,7 @@ fun main() = application {
val contours = findContours(
f = {
val triangle = triangles.firstOrNull { triangle ->
isPointInTriangle(it, listOf(triangle.x1, triangle.x2, triangle.x3))
isPointInTriangle3D(it, listOf(triangle.x1, triangle.x2, triangle.x3))
}
triangle ?: return@findContours 0.0
val interpolate = interpolateHeight(
@@ -178,9 +175,7 @@ fun main() = application {
triangle.x1,
triangle.x2,
triangle.x3,
).map {
Vector3(it.x, it.y, associate[it]!!)
}
)
)
interpolate.z - targetHeight
},
@@ -191,6 +186,7 @@ fun main() = application {
if (logEnabled) println("useInterpolation = $useInterpolation")
drawer.stroke = null
contours.map {
if (false) drawer.contour(it)
it.segments.map {
Segment3D(
it.start.vector3(),