mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
samples: add software 3D renderer in Kotlin
This commit is contained in:
parent
932f33ae2c
commit
01cf27b3d8
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.8.0"
|
||||
kotlin("multiplatform") version "1.9.20"
|
||||
war
|
||||
id("org.teavm")
|
||||
}
|
||||
|
@ -28,3 +28,7 @@ teavm.js {
|
|||
addedToWebApp = true
|
||||
mainClass = "org.teavm.samples.kotlin.HelloKt"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
}
|
|
@ -59,6 +59,7 @@ include("pi")
|
|||
include("kotlin")
|
||||
include("scala")
|
||||
include("web-apis")
|
||||
include("software3d")
|
||||
|
||||
gradle.allprojects {
|
||||
apply<WarPlugin>()
|
||||
|
|
67
samples/software3d/build.gradle.kts
Normal file
67
samples/software3d/build.gradle.kts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import org.teavm.gradle.api.OptimizationLevel
|
||||
import org.teavm.gradle.tasks.TeaVMTask
|
||||
|
||||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "1.9.20"
|
||||
war
|
||||
id("org.teavm")
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
teavm.js {
|
||||
addedToWebApp = true
|
||||
mainClass = "org.teavm.samples.software3d.teavm.MainKt"
|
||||
}
|
||||
|
||||
teavm.wasm {
|
||||
addedToWebApp = true
|
||||
mainClass = "org.teavm.samples.software3d.teavm.WasmWorkerKt"
|
||||
optimization = OptimizationLevel.AGGRESSIVE
|
||||
minHeapSize = 4
|
||||
}
|
||||
|
||||
kotlin {
|
||||
js {
|
||||
browser {
|
||||
|
||||
}
|
||||
binaries.executable()
|
||||
}
|
||||
jvm()
|
||||
sourceSets.jvmMain.dependencies {
|
||||
implementation(teavm.libs.jsoApis)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<TeaVMTask> {
|
||||
classpath.from(kotlin.jvm().compilations["main"].output.classesDirs)
|
||||
}
|
||||
|
||||
tasks.war {
|
||||
dependsOn(tasks.named("jsBrowserDistribution"))
|
||||
with(copySpec {
|
||||
from(kotlin.js().binaries.executable().map { it.distribution.outputDirectory })
|
||||
into("kjs")
|
||||
})
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.geometry
|
||||
|
||||
import org.teavm.samples.software3d.scene.Vertex
|
||||
|
||||
class Face(val a: Vertex, val b: Vertex, val c: Vertex)
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.geometry
|
||||
|
||||
interface GeneralMatrix {
|
||||
val size: Int
|
||||
|
||||
fun get(row: Int, col: Int): Float
|
||||
|
||||
fun determinant(): Float {
|
||||
return if (size == 2) {
|
||||
get(0, 0) * get(1, 1) - get(1, 0) * get(0, 1)
|
||||
} else {
|
||||
(0 until size)
|
||||
.map { j ->
|
||||
val r = get(0, j) * exclude(0, j).determinant()
|
||||
if (j % 2 == 0) r else -r
|
||||
}
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
fun exclude(excludeRow: Int, excludeCol: Int): GeneralMatrix {
|
||||
val orig = this
|
||||
return object : GeneralMatrix {
|
||||
override val size: Int
|
||||
get() = orig.size - 1
|
||||
|
||||
override fun get(row: Int, col: Int): Float {
|
||||
return orig.get(if (row < excludeRow) row else row + 1, if (col < excludeCol) col else col + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun asString(): String {
|
||||
val cells = (0 until size).map { j ->
|
||||
(0 until size).map { i ->
|
||||
get(i, j).toString()
|
||||
}
|
||||
}
|
||||
val lengths = cells.map { column -> column.maxOf { it.length } }
|
||||
val padCells = (cells zip lengths).map { (column, length) ->
|
||||
column.map { it + " ".repeat(length - it.length) }
|
||||
}
|
||||
|
||||
return (0 until size).joinToString("\n") { i ->
|
||||
(0 until size).joinToString(" ") { j -> padCells[j][i] }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.geometry
|
||||
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
|
||||
class Matrix(
|
||||
val m11: Float, val m12: Float, val m13: Float, val m14: Float,
|
||||
val m21: Float, val m22: Float, val m23: Float, val m24: Float,
|
||||
val m31: Float, val m32: Float, val m33: Float, val m34: Float,
|
||||
val m41: Float, val m42: Float, val m43: Float, val m44: Float
|
||||
) : GeneralMatrix {
|
||||
override val size: Int
|
||||
get() = 4
|
||||
|
||||
override fun get(row: Int, col: Int): Float {
|
||||
val offset = row * 4 + col
|
||||
return when (offset) {
|
||||
0 -> m11
|
||||
1 -> m12
|
||||
2 -> m13
|
||||
3 -> m14
|
||||
4 -> m21
|
||||
5 -> m22
|
||||
6 -> m23
|
||||
7 -> m24
|
||||
8 -> m31
|
||||
9 -> m32
|
||||
10 -> m33
|
||||
11 -> m34
|
||||
12 -> m41
|
||||
13 -> m42
|
||||
14 -> m43
|
||||
15 -> m44
|
||||
else -> error("Wrong row/column")
|
||||
}
|
||||
}
|
||||
|
||||
fun applyTo(v: Vector, result: MutableVector) {
|
||||
with(result) {
|
||||
x = m11 * v.x + m12 * v.y + m13 * v.z + m14 * v.w
|
||||
y = m21 * v.x + m22 * v.y + m23 * v.z + m24 * v.w
|
||||
z = m31 * v.x + m32 * v.y + m33 * v.z + m34 * v.w
|
||||
w = m41 * v.x + m42 * v.y + m43 * v.z + m44 * v.w
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun applyDirTo(v: Vector, result: MutableVector) {
|
||||
with(result) {
|
||||
x = m11 * v.x + m12 * v.y + m13 * v.z
|
||||
y = m21 * v.x + m22 * v.y + m23 * v.z
|
||||
z = m31 * v.x + m32 * v.y + m33 * v.z
|
||||
w = m41 * v.x + m42 * v.y + m43 * v.z
|
||||
}
|
||||
}
|
||||
|
||||
operator fun times(v: Vector): Vector = Vector(
|
||||
x = m11 * v.x + m12 * v.y + m13 * v.z + m14 * v.w,
|
||||
y = m21 * v.x + m22 * v.y + m23 * v.z + m24 * v.w,
|
||||
z = m31 * v.x + m32 * v.y + m33 * v.z + m34 * v.w,
|
||||
w = m41 * v.x + m42 * v.y + m43 * v.z + m44 * v.w,
|
||||
)
|
||||
|
||||
operator fun times(m: Matrix): Matrix = Matrix(
|
||||
m11 = m11 * m.m11 + m12 * m.m21 + m13 * m.m31 + m14 * m.m41,
|
||||
m12 = m11 * m.m12 + m12 * m.m22 + m13 * m.m32 + m14 * m.m42,
|
||||
m13 = m11 * m.m13 + m12 * m.m23 + m13 * m.m33 + m14 * m.m43,
|
||||
m14 = m11 * m.m14 + m12 * m.m24 + m13 * m.m34 + m14 * m.m44,
|
||||
m21 = m21 * m.m11 + m22 * m.m21 + m23 * m.m31 + m24 * m.m41,
|
||||
m22 = m21 * m.m12 + m22 * m.m22 + m23 * m.m32 + m24 * m.m42,
|
||||
m23 = m21 * m.m13 + m22 * m.m23 + m23 * m.m33 + m24 * m.m43,
|
||||
m24 = m21 * m.m14 + m22 * m.m24 + m23 * m.m34 + m24 * m.m44,
|
||||
m31 = m31 * m.m11 + m32 * m.m21 + m33 * m.m31 + m34 * m.m41,
|
||||
m32 = m31 * m.m12 + m32 * m.m22 + m33 * m.m32 + m34 * m.m42,
|
||||
m33 = m31 * m.m13 + m32 * m.m23 + m33 * m.m33 + m34 * m.m43,
|
||||
m34 = m31 * m.m14 + m32 * m.m24 + m33 * m.m34 + m34 * m.m44,
|
||||
m41 = m41 * m.m11 + m42 * m.m21 + m43 * m.m31 + m44 * m.m41,
|
||||
m42 = m41 * m.m12 + m42 * m.m22 + m43 * m.m32 + m44 * m.m42,
|
||||
m43 = m41 * m.m13 + m42 * m.m23 + m43 * m.m33 + m44 * m.m43,
|
||||
m44 = m41 * m.m14 + m42 * m.m24 + m43 * m.m34 + m44 * m.m44
|
||||
)
|
||||
|
||||
fun transpose(): Matrix = Matrix(
|
||||
m11 = m11, m12 = m21, m13 = m31, m14 = m41,
|
||||
m21 = m12, m22 = m22, m23 = m32, m24 = m42,
|
||||
m31 = m13, m32 = m23, m33 = m33, m34 = m43,
|
||||
m41 = m14, m42 = m24, m43 = m34, m44 = m44
|
||||
)
|
||||
|
||||
fun inverse(): Matrix {
|
||||
val transposed = transpose()
|
||||
val determinant = determinant()
|
||||
return generate { i, j ->
|
||||
val minor = transposed.exclude(i, j).determinant()
|
||||
val adj = if ((i + j) % 2 == 0) minor else -minor
|
||||
adj / determinant
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun projection(
|
||||
left: Float,
|
||||
right: Float,
|
||||
bottom: Float,
|
||||
top: Float,
|
||||
near: Float,
|
||||
far: Float
|
||||
): Matrix = Matrix(
|
||||
m11 = 2 * near / (right - left),
|
||||
m12 = 0f,
|
||||
m13 = (right + left) / (right - left),
|
||||
m14 = 0f,
|
||||
m21 = 0f,
|
||||
m22 = 2 * near / (top - bottom),
|
||||
m23 = (top + bottom) / (top - bottom),
|
||||
m24 = 0f,
|
||||
m31 = 0f,
|
||||
m32 = 0f,
|
||||
m33 = -(far + near) / (far - near),
|
||||
m34 = -2 * far * near / (far - near),
|
||||
m41 = 0f,
|
||||
m42 = 0f,
|
||||
m43 = -1f,
|
||||
m44 = 0f
|
||||
)
|
||||
|
||||
fun rotation(x: Float, y: Float, z: Float, angle: Float): Matrix {
|
||||
val length = length(x, y, z)
|
||||
val nx = x / length
|
||||
val ny = y / length
|
||||
val nz = z / length
|
||||
val c = cos(angle)
|
||||
val s = sin(angle)
|
||||
return Matrix(
|
||||
m11 = (1 - c) * nx * nx + c,
|
||||
m12 = (1 - c) * nx * ny - s * nz,
|
||||
m13 = (1 - c) * nx * nz + s * ny,
|
||||
m14 = 0f,
|
||||
m21 = (1 - c) * nx * ny + s * nz,
|
||||
m22 = (1 - c) * ny * ny + c,
|
||||
m23 = (1 - c) * ny * nz - s * nx,
|
||||
m24 = 0f,
|
||||
m31 = (1 - c) * nx * nz - s * ny,
|
||||
m32 = (1 - c) * ny * nz + s * nx,
|
||||
m33 = (1 - c) * nz * nz + c,
|
||||
m34 = 0f,
|
||||
m41 = 0f,
|
||||
m42 = 0f,
|
||||
m43 = 0f,
|
||||
m44 = 1f
|
||||
)
|
||||
}
|
||||
|
||||
fun translation(x: Float, y: Float, z: Float): Matrix = Matrix(
|
||||
m11 = 1f, m12 = 0f, m13 = 0f, m14 = x,
|
||||
m21 = 0f, m22 = 1f, m23 = 0f, m24 = y,
|
||||
m31 = 0f, m32 = 0f, m33 = 1f, m34 = z,
|
||||
m41 = 0f, m42 = 0f, m43 = 0f, m44 = 1f
|
||||
)
|
||||
|
||||
fun scale(x: Float, y: Float, z: Float): Matrix = Matrix(
|
||||
m11 = x, m12 = 0f, m13 = 0f, m14 = 0f,
|
||||
m21 = 0f, m22 = y, m23 = 0f, m24 = 0f,
|
||||
m31 = 0f, m32 = 0f, m33 = z, m34 = 0f,
|
||||
m41 = 0f, m42 = 0f, m43 = 0f, m44 = 1f
|
||||
)
|
||||
|
||||
fun lookAt(dirX: Float, dirY: Float, dirZ: Float): Matrix {
|
||||
val forward = Vector(dirX, dirY, dirZ, 0f).norm()
|
||||
val left = (Vector(0f, 1f, 0f, 0f) cross forward).norm()
|
||||
val up = forward cross left
|
||||
return Matrix(
|
||||
m11 = left.x, m12 = up.x, m13 = forward.x, m14 = 0f,
|
||||
m21 = left.y, m22 = up.y, m23 = forward.y, m24 = 0f,
|
||||
m31 = left.z, m32 = up.z, m33 = forward.z, m34 = 0f,
|
||||
m41 = 0f, m42 = 0f, m43 = 0f, m44 = 1f
|
||||
)
|
||||
}
|
||||
|
||||
fun lookAt(x: Float, y: Float, z: Float, dirX: Float, dirY: Float, dirZ: Float): Matrix {
|
||||
return translation(x, y, z) * lookAt(dirX, dirY, dirZ)
|
||||
}
|
||||
|
||||
fun generate(generator: (Int, Int) -> Float): Matrix = Matrix(
|
||||
m11 = generator(0, 0), m12 = generator(0, 1), m13 = generator(0, 2), m14 = generator(0, 3),
|
||||
m21 = generator(1, 0), m22 = generator(1, 1), m23 = generator(1, 2), m24 = generator(1, 3),
|
||||
m31 = generator(2, 0), m32 = generator(2, 1), m33 = generator(2, 2), m34 = generator(2, 3),
|
||||
m41 = generator(3, 0), m42 = generator(3, 1), m43 = generator(3, 2), m44 = generator(3, 3)
|
||||
)
|
||||
|
||||
val identity: Matrix = Matrix(
|
||||
m11 = 1f, m12 = 0f, m13 = 0f, m14 = 0f,
|
||||
m21 = 0f, m22 = 1f, m23 = 0f, m24 = 0f,
|
||||
m31 = 0f, m32 = 0f, m33 = 1f, m34 = 0f,
|
||||
m41 = 0f, m42 = 0f, m43 = 0f, m44 = 1f
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.geometry
|
||||
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
class MutableVector {
|
||||
@JvmField
|
||||
var x: Float = 0f
|
||||
@JvmField
|
||||
var y: Float = 0f
|
||||
@JvmField
|
||||
var z: Float = 0f
|
||||
@JvmField
|
||||
var w: Float = 0f
|
||||
|
||||
fun set(vector: Vector) {
|
||||
x = vector.x
|
||||
y = vector.y
|
||||
z = vector.z
|
||||
w = vector.w
|
||||
}
|
||||
|
||||
fun normalizeW() {
|
||||
x /= w
|
||||
y /= w
|
||||
z /= w
|
||||
w = 1f
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.geometry
|
||||
|
||||
import kotlin.jvm.JvmField
|
||||
import kotlin.math.sqrt
|
||||
|
||||
class Vector(
|
||||
@JvmField val x: Float,
|
||||
@JvmField val y: Float,
|
||||
@JvmField val z: Float,
|
||||
@JvmField val w: Float
|
||||
) {
|
||||
operator fun unaryMinus(): Vector {
|
||||
return Vector(-x, -y, -z, -w)
|
||||
}
|
||||
|
||||
override fun toString(): String = "$x, $y, $z, $w"
|
||||
|
||||
infix fun cross(other: Vector): Vector = Vector(
|
||||
x = y * other.z - z * other.y,
|
||||
y = z * other.x - x * other.z,
|
||||
z = x * other.y - y * other.x,
|
||||
w = 0f
|
||||
)
|
||||
|
||||
fun length(): Float = sqrt(x * x + y * y + z * z + w * w)
|
||||
|
||||
fun norm(): Vector {
|
||||
val l = length()
|
||||
return Vector(x / l, y / l, z / l, w / l)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val zero: Vector = Vector(0f, 0f, 0f, 1f)
|
||||
val axisX: Vector = Vector(1f, 0f, 0f, 1f)
|
||||
val axisY: Vector = Vector(0f, 1f, 0f, 1f)
|
||||
val axisZ: Vector = Vector(0f, 0f, 1f, 1f)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.geometry
|
||||
|
||||
import kotlin.math.sqrt
|
||||
|
||||
fun length(x: Float, y: Float, z: Float): Float = sqrt(x * x + y * y + z * z)
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.rasterization
|
||||
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
class Raster(@JvmField val width: Int, @JvmField val height: Int) {
|
||||
@JvmField var color: IntArray = IntArray(width * height)
|
||||
@JvmField val depth: FloatArray = FloatArray(width * height)
|
||||
|
||||
fun pointer(x: Int, y: Int): Int = y * width + x
|
||||
|
||||
fun clear() {
|
||||
color.fill(255 shl 24)
|
||||
depth.fill(0f)
|
||||
}
|
||||
|
||||
fun flip(): IntArray {
|
||||
val result = color
|
||||
color = IntArray(width * height)
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.rasterization
|
||||
|
||||
import org.teavm.samples.software3d.geometry.Vector
|
||||
import org.teavm.samples.software3d.geometry.length
|
||||
import kotlin.math.ceil
|
||||
|
||||
class Rasterizer(val raster: Raster, val offset: Int, val step: Int) {
|
||||
var ambient: Vector = Vector.zero
|
||||
var lightPosition: Vector = Vector.zero
|
||||
var lightColor: Vector = Vector.zero
|
||||
|
||||
fun drawTriangle(a: VertexParams, b: VertexParams, c: VertexParams) {
|
||||
val v1: VertexParams
|
||||
val v2: VertexParams
|
||||
val v3: VertexParams
|
||||
if (a.pos.y < b.pos.y) {
|
||||
if (a.pos.y < c.pos.y) {
|
||||
v1 = a
|
||||
if (b.pos.y < c.pos.y) {
|
||||
v2 = b
|
||||
v3 = c
|
||||
} else {
|
||||
v2 = c
|
||||
v3 = b
|
||||
}
|
||||
} else {
|
||||
v1 = c
|
||||
v2 = a
|
||||
v3 = b
|
||||
}
|
||||
} else {
|
||||
if (b.pos.y < c.pos.y) {
|
||||
v1 = b
|
||||
if (c.pos.y < a.pos.y) {
|
||||
v2 = c
|
||||
v3 = a
|
||||
} else {
|
||||
v2 = a
|
||||
v3 = c
|
||||
}
|
||||
} else {
|
||||
v1 = c
|
||||
v2 = b
|
||||
v3 = a
|
||||
}
|
||||
}
|
||||
|
||||
val v3x = if (v2.pos.y == v3.pos.y) {
|
||||
v3.pos.x
|
||||
} else {
|
||||
v1.pos.x + (v2.pos.y - v1.pos.y) * (v3.pos.x - v1.pos.x) / (v3.pos.y - v1.pos.y)
|
||||
}
|
||||
if (v2.pos.x < v3x) {
|
||||
drawTrianglePart(v1, v2, v1, v3, ceil(v1.pos.y).toInt(), ceil(v2.pos.y).toInt())
|
||||
drawTrianglePart(v2, v3, v1, v3, ceil(v2.pos.y).toInt(), ceil(v3.pos.y).toInt())
|
||||
} else {
|
||||
drawTrianglePart(v1, v3, v1, v2, ceil(v1.pos.y).toInt(), ceil(v2.pos.y).toInt())
|
||||
drawTrianglePart(v1, v3, v2, v3, ceil(v2.pos.y).toInt(), ceil(v3.pos.y).toInt())
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalizeY(y: Int): Int = ((y + step - 1 - offset) / step) * step + offset
|
||||
|
||||
private fun drawTrianglePart(
|
||||
s1: VertexParams, e1: VertexParams,
|
||||
s2: VertexParams, e2: VertexParams,
|
||||
sy: Int, ey: Int
|
||||
) {
|
||||
val nsy = normalizeY(sy).coerceAtLeast(0)
|
||||
val ney = normalizeY(ey).coerceAtMost(raster.height * step)
|
||||
if (ney <= 0 || nsy >= raster.height * step) {
|
||||
return
|
||||
}
|
||||
|
||||
val d1x = e1.pos.x - s1.pos.x
|
||||
val d1y = e1.pos.y - s1.pos.y
|
||||
val d1z = e1.pos.z - s1.pos.z
|
||||
val d2x = e2.pos.x - s2.pos.x
|
||||
val d2y = e2.pos.y - s2.pos.y
|
||||
val d2z = e2.pos.z - s2.pos.z
|
||||
|
||||
var y = nsy
|
||||
while (y < ney) {
|
||||
val k1 = if (d1y == 0f) 0f else (y - s1.pos.y) / d1y
|
||||
val k2 = if (d2y == 0f) 0f else (y - s2.pos.y) / d2y
|
||||
|
||||
val sx = s1.pos.x + d1x * k1
|
||||
val ex = s2.pos.x + d2x * k2
|
||||
val startIntX = ceil(sx).toInt().coerceAtLeast(0)
|
||||
val endIntX = ceil(ex).toInt().coerceAtMost(raster.width)
|
||||
if (startIntX + 1 == endIntX || startIntX >= raster.width || endIntX <= 0) {
|
||||
y += step
|
||||
continue
|
||||
}
|
||||
|
||||
val sz = s1.pos.z + d1z * k1
|
||||
val sar = s1.ambient.x + (e1.ambient.x - s1.ambient.x) * k1
|
||||
val sag = s1.ambient.y + (e1.ambient.y - s1.ambient.y) * k1
|
||||
val sab = s1.ambient.z + (e1.ambient.z - s1.ambient.z) * k1
|
||||
val sdr = s1.diffuse.x + (e1.diffuse.x - s1.diffuse.x) * k1
|
||||
val sdg = s1.diffuse.y + (e1.diffuse.y - s1.diffuse.y) * k1
|
||||
val sdb = s1.diffuse.z + (e1.diffuse.z - s1.diffuse.z) * k1
|
||||
val snx = s1.normal.x + (e1.normal.x - s1.normal.x) * k1
|
||||
val sny = s1.normal.y + (e1.normal.y - s1.normal.y) * k1
|
||||
val snz = s1.normal.z + (e1.normal.z - s1.normal.z) * k1
|
||||
val sox = s1.orig.x + (e1.orig.x - s1.orig.x) * k1
|
||||
val soy = s1.orig.y + (e1.orig.y - s1.orig.y) * k1
|
||||
val soz = s1.orig.z + (e1.orig.z - s1.orig.z) * k1
|
||||
|
||||
val ez = s2.pos.z + d2z * k2
|
||||
val ear = s2.ambient.x + (e2.ambient.x - s2.ambient.x) * k2
|
||||
val eag = s2.ambient.y + (e2.ambient.y - s2.ambient.y) * k2
|
||||
val eab = s2.ambient.z + (e2.ambient.z - s2.ambient.z) * k2
|
||||
val edr = s2.diffuse.x + (e2.diffuse.x - s2.diffuse.x) * k2
|
||||
val edg = s2.diffuse.y + (e2.diffuse.y - s2.diffuse.y) * k2
|
||||
val edb = s2.diffuse.z + (e2.diffuse.z - s2.diffuse.z) * k2
|
||||
val enx = s2.normal.x + (e2.normal.x - s2.normal.x) * k2
|
||||
val eny = s2.normal.y + (e2.normal.y - s2.normal.y) * k2
|
||||
val enz = s2.normal.z + (e2.normal.z - s2.normal.z) * k2
|
||||
val eox = s2.orig.x + (e2.orig.x - s2.orig.x) * k2
|
||||
val eoy = s2.orig.y + (e2.orig.y - s2.orig.y) * k2
|
||||
val eoz = s2.orig.z + (e2.orig.z - s2.orig.z) * k2
|
||||
|
||||
var ptr = raster.pointer(startIntX, y / step)
|
||||
var x = startIntX
|
||||
while (x < endIntX) {
|
||||
val z = sz + (ez - sz) * (x - sx) / (ex - sx)
|
||||
if (z > raster.depth[ptr]) {
|
||||
raster.depth[ptr] = z
|
||||
|
||||
val k = if (sx == ex) 0f else (x - sx) / (ex - sx)
|
||||
val ar = sar + (ear - sar) * k
|
||||
val ag = sag + (eag - sag) * k
|
||||
val ab = sab + (eab - sab) * k
|
||||
val dr = sdr + (edr - sdr) * k
|
||||
val dg = sdg + (edg - sdg) * k
|
||||
val db = sdb + (edb - sdb) * k
|
||||
val nx = snx + (enx - snx) * k
|
||||
val ny = sny + (eny - sny) * k
|
||||
val nz = snz + (enz - snz) * k
|
||||
val ox = sox + (eox - sox) * k
|
||||
val oy = soy + (eoy - soy) * k
|
||||
val oz = soz + (eoz - soz) * k
|
||||
|
||||
val lightDirX = lightPosition.x - ox
|
||||
val lightDirY = lightPosition.y - oy
|
||||
val lightDirZ = lightPosition.z - oz
|
||||
val lightDirLength = length(lightDirX, lightDirY, lightDirZ)
|
||||
val normalLength = length(nx, ny, nz)
|
||||
var cosAngle = (nx * lightDirX + ny * lightDirY + nz * lightDirZ) / (lightDirLength * normalLength)
|
||||
cosAngle = cosAngle.coerceAtLeast(0f)
|
||||
|
||||
val r = ar * ambient.x + dr * lightColor.x * cosAngle
|
||||
val g = ag * ambient.y + dg * lightColor.y * cosAngle
|
||||
val b = ab * ambient.z + db * lightColor.z * cosAngle
|
||||
val intR = (r * 255).toInt().coerceIn(0, 255)
|
||||
val intG = (g * 255).toInt().coerceIn(0, 255)
|
||||
val intB = (b * 255).toInt().coerceIn(0, 255)
|
||||
raster.color[ptr] = intB or (intG shl 8) or (intR shl 16) or (255 shl 24)
|
||||
}
|
||||
++ptr
|
||||
++x
|
||||
}
|
||||
|
||||
y += step
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.rasterization
|
||||
|
||||
import org.teavm.samples.software3d.geometry.MutableVector
|
||||
import org.teavm.samples.software3d.geometry.Vector
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
class VertexParams {
|
||||
@JvmField
|
||||
val pos: MutableVector = MutableVector()
|
||||
@JvmField
|
||||
val orig: MutableVector = MutableVector()
|
||||
@JvmField
|
||||
val normal: MutableVector = MutableVector()
|
||||
@JvmField
|
||||
var ambient: Vector = Vector.zero
|
||||
@JvmField
|
||||
var diffuse: Vector = Vector.zero
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.rendering
|
||||
|
||||
import org.teavm.samples.software3d.geometry.Matrix
|
||||
import org.teavm.samples.software3d.rasterization.Raster
|
||||
import org.teavm.samples.software3d.rasterization.Rasterizer
|
||||
import org.teavm.samples.software3d.rasterization.VertexParams
|
||||
import org.teavm.samples.software3d.scene.Item
|
||||
import org.teavm.samples.software3d.scene.Scene
|
||||
|
||||
class Renderer(val scene: Scene, val raster: Raster, offset: Int, step: Int) {
|
||||
private val rasterizer = Rasterizer(raster, offset, step)
|
||||
|
||||
var projection: Matrix = Matrix.identity
|
||||
var viewport: Matrix = Matrix.identity
|
||||
private var transform: Matrix = Matrix.identity
|
||||
private var v1 = VertexParams()
|
||||
private var v2 = VertexParams()
|
||||
private var v3 = VertexParams()
|
||||
|
||||
fun render() {
|
||||
transform = viewport * projection * scene.camera
|
||||
raster.clear()
|
||||
rasterizer.lightPosition = scene.camera * scene.lightPosition
|
||||
rasterizer.lightColor = scene.lightColor
|
||||
rasterizer.ambient = scene.ambientColor
|
||||
for (item in scene.items) {
|
||||
renderItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderItem(item: Item) {
|
||||
val itemTransform = scene.camera * item.transform
|
||||
val viewItemTransform = transform * item.transform
|
||||
for (face in item.mesh.faces) {
|
||||
itemTransform.applyTo(face.a.position, v1.orig)
|
||||
itemTransform.applyTo(face.b.position, v2.orig)
|
||||
itemTransform.applyTo(face.c.position, v3.orig)
|
||||
|
||||
viewItemTransform.applyTo(face.a.position, v1.pos)
|
||||
viewItemTransform.applyTo(face.b.position, v2.pos)
|
||||
viewItemTransform.applyTo(face.c.position, v3.pos)
|
||||
|
||||
itemTransform.applyDirTo(face.a.normal, v1.normal)
|
||||
itemTransform.applyDirTo(face.b.normal, v2.normal)
|
||||
itemTransform.applyDirTo(face.c.normal, v3.normal)
|
||||
|
||||
v1.diffuse = face.a.diffuse
|
||||
v1.ambient = face.a.ambient
|
||||
v2.diffuse = face.b.diffuse
|
||||
v2.ambient = face.b.ambient
|
||||
v3.diffuse = face.c.diffuse
|
||||
v3.ambient = face.c.ambient
|
||||
|
||||
v1.pos.normalizeW()
|
||||
v2.pos.normalizeW()
|
||||
v3.pos.normalizeW()
|
||||
|
||||
rasterizer.drawTriangle(v1, v2, v3)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.scene
|
||||
|
||||
import org.teavm.samples.software3d.geometry.Matrix
|
||||
|
||||
class Item(val mesh: Mesh) {
|
||||
var transform: Matrix = Matrix.identity
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.scene
|
||||
|
||||
import org.teavm.samples.software3d.geometry.Face
|
||||
|
||||
class Mesh(
|
||||
val faces: List<Face>
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.scene
|
||||
|
||||
import org.teavm.samples.software3d.geometry.Matrix
|
||||
import org.teavm.samples.software3d.geometry.Vector
|
||||
|
||||
class Scene {
|
||||
val items: MutableList<Item> = mutableListOf()
|
||||
var camera: Matrix = Matrix.identity
|
||||
var lightPosition: Vector = Vector.zero
|
||||
var lightColor: Vector = Vector.zero
|
||||
var ambientColor: Vector = Vector.zero
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.scene
|
||||
|
||||
import org.teavm.samples.software3d.geometry.Vector
|
||||
|
||||
class Vertex(
|
||||
val position: Vector,
|
||||
val normal: Vector,
|
||||
val ambient: Vector,
|
||||
val diffuse: Vector
|
||||
)
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.scenes
|
||||
|
||||
import org.teavm.samples.software3d.geometry.Matrix
|
||||
import org.teavm.samples.software3d.geometry.Vector
|
||||
import org.teavm.samples.software3d.scene.Item
|
||||
import org.teavm.samples.software3d.scene.Scene
|
||||
import kotlin.math.PI
|
||||
|
||||
fun geometry(): Pair<Scene, (Double) -> Unit> {
|
||||
val scene = Scene()
|
||||
|
||||
val cubeConstantTransform = Matrix.scale(1.4f, 1.4f, 1.4f) * Matrix.rotation(1f, 0f, 0f, PI.toFloat() / 4)
|
||||
val cubeItem = Item(Meshes.cube())
|
||||
scene.items += cubeItem
|
||||
|
||||
val sphere1 = Item(Meshes.sphere())
|
||||
val sphere1ConstantTransform = Matrix.translation(2f, 0f, 0f) * Matrix.scale(0.2f, 0.2f, 0.2f)
|
||||
scene.items += sphere1
|
||||
|
||||
val sphere2 = Item(Meshes.sphere())
|
||||
val sphere2ConstantTransform = Matrix.translation(-3.5f, 0f, 0f) * Matrix.scale(0.3f, 0.3f, 0.3f)
|
||||
scene.items += sphere2
|
||||
|
||||
scene.camera = Matrix.lookAt(0f, 2f, -7f, 0f, -0.3f, 1f).inverse()
|
||||
scene.lightColor = Vector(0.5f, 0.5f, 0.5f, 0.5f)
|
||||
scene.lightPosition = Vector(3f, 0.5f, -2f, 1f)
|
||||
scene.ambientColor = Vector(0.5f, 0.5f, 0.5f, 0.5f)
|
||||
|
||||
return Pair(scene) { time ->
|
||||
val cubeVarTransform = Matrix.rotation(0f, 1f, 0f, (PI * time / 3).toFloat())
|
||||
cubeItem.transform = cubeVarTransform * cubeConstantTransform
|
||||
|
||||
val sphere1VarTransform = Matrix.rotation(0f, 1f, -0.5f, (-PI * time).toFloat())
|
||||
sphere1.transform = sphere1VarTransform * sphere1ConstantTransform
|
||||
|
||||
val sphere2VarTransform = Matrix.rotation(0.5f, 1f, 0.5f, (-PI * time / 10).toFloat())
|
||||
sphere2.transform = sphere2VarTransform * sphere2ConstantTransform
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.scenes
|
||||
|
||||
import org.teavm.samples.software3d.geometry.Face
|
||||
import org.teavm.samples.software3d.geometry.Vector
|
||||
import org.teavm.samples.software3d.scene.Mesh
|
||||
import org.teavm.samples.software3d.scene.Vertex
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
object Meshes {
|
||||
fun cube(): Mesh {
|
||||
val positions = listOf(
|
||||
Vector(-1f, -1f, -1f, 1f),
|
||||
Vector(1f, -1f, -1f, 1f),
|
||||
Vector(1f, 1f, -1f, 1f),
|
||||
Vector(-1f, 1f, -1f, 1f),
|
||||
Vector(-1f, -1f, 1f, 1f),
|
||||
Vector(1f, -1f, 1f, 1f),
|
||||
Vector(1f, 1f, 1f, 1f),
|
||||
Vector(-1f, 1f, 1f, 1f)
|
||||
)
|
||||
val red = Vector(1f, 0f, 0f, 1f)
|
||||
val green = Vector(0f, 1f, 0f, 1f)
|
||||
val blue = Vector(0f, 0f, 1f, 1f)
|
||||
val yellow = Vector(1f, 1f, 0f, 1f)
|
||||
val cyan = Vector(0f, 1f, 1f, 1f)
|
||||
val magenta = Vector(1f, 0f, 1f, 1f)
|
||||
return Mesh(listOf(
|
||||
Face(
|
||||
Vertex(positions[0], -Vector.axisZ, red, red),
|
||||
Vertex(positions[1], -Vector.axisZ, red, red),
|
||||
Vertex(positions[2], -Vector.axisZ, red, red)
|
||||
),
|
||||
Face(
|
||||
Vertex(positions[0], -Vector.axisZ, red, red),
|
||||
Vertex(positions[2], -Vector.axisZ, red, red),
|
||||
Vertex(positions[3], -Vector.axisZ, red, red)
|
||||
),
|
||||
|
||||
Face(
|
||||
Vertex(positions[4], Vector.axisZ, cyan, cyan),
|
||||
Vertex(positions[5], Vector.axisZ, cyan, cyan),
|
||||
Vertex(positions[6], Vector.axisZ, cyan, cyan)
|
||||
),
|
||||
Face(
|
||||
Vertex(positions[4], Vector.axisZ, cyan, cyan),
|
||||
Vertex(positions[7], Vector.axisZ, cyan, cyan),
|
||||
Vertex(positions[6], Vector.axisZ, cyan, cyan)
|
||||
),
|
||||
|
||||
Face(
|
||||
Vertex(positions[0], -Vector.axisY, yellow, yellow),
|
||||
Vertex(positions[1], -Vector.axisY, yellow, yellow),
|
||||
Vertex(positions[5], -Vector.axisY, yellow, yellow)
|
||||
),
|
||||
Face(
|
||||
Vertex(positions[0], -Vector.axisY, yellow, yellow),
|
||||
Vertex(positions[4], -Vector.axisY, yellow, yellow),
|
||||
Vertex(positions[5], -Vector.axisY, yellow, yellow)
|
||||
),
|
||||
|
||||
Face(
|
||||
Vertex(positions[2], Vector.axisY, blue, blue),
|
||||
Vertex(positions[3], Vector.axisY, blue, blue),
|
||||
Vertex(positions[7], Vector.axisY, blue, blue)
|
||||
),
|
||||
Face(
|
||||
Vertex(positions[2], Vector.axisY, blue, blue),
|
||||
Vertex(positions[6], Vector.axisY, blue, blue),
|
||||
Vertex(positions[7], Vector.axisY, blue, blue)
|
||||
),
|
||||
|
||||
Face(
|
||||
Vertex(positions[0], -Vector.axisX, green, green),
|
||||
Vertex(positions[3], -Vector.axisX, green, green),
|
||||
Vertex(positions[4], -Vector.axisX, green, green)
|
||||
),
|
||||
Face(
|
||||
Vertex(positions[3], -Vector.axisX, green, green),
|
||||
Vertex(positions[4], -Vector.axisX, green, green),
|
||||
Vertex(positions[7], -Vector.axisX, green, green)
|
||||
),
|
||||
|
||||
Face(
|
||||
Vertex(positions[1], Vector.axisX, magenta, magenta),
|
||||
Vertex(positions[2], Vector.axisX, magenta, magenta),
|
||||
Vertex(positions[5], Vector.axisX, magenta, magenta)
|
||||
),
|
||||
Face(
|
||||
Vertex(positions[2], Vector.axisX, magenta, magenta),
|
||||
Vertex(positions[5], Vector.axisX, magenta, magenta),
|
||||
Vertex(positions[6], Vector.axisX, magenta, magenta)
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
fun sphere(): Mesh {
|
||||
val count = 5
|
||||
val rows = count * 4
|
||||
val columns = count * 4
|
||||
val color = Vector(1f, 1f, 1f, 1f)
|
||||
val vertices = (0 until rows).map { i ->
|
||||
val latitude = i * PI / 2 / count
|
||||
val latX = cos(latitude)
|
||||
val latY = sin(latitude)
|
||||
(0 until columns).map { j ->
|
||||
val longitude = j * PI / 2 / count
|
||||
val longX = cos(longitude)
|
||||
val longY = sin(longitude)
|
||||
val pos = Vector(
|
||||
x = (longX * latX).toFloat(),
|
||||
z = (longY * latX).toFloat(),
|
||||
y = latY.toFloat(),
|
||||
w = 1f
|
||||
)
|
||||
Vertex(
|
||||
position = pos,
|
||||
normal = pos,
|
||||
ambient = color,
|
||||
diffuse = color
|
||||
)
|
||||
}
|
||||
}
|
||||
val faces = (0 until rows).flatMap { row ->
|
||||
(0 until columns).flatMap { col ->
|
||||
val a = vertices[row][col]
|
||||
val b = vertices[(row + 1) % rows][col]
|
||||
val c = vertices[(row + 1) % rows][(col + 1) % columns]
|
||||
val d = vertices[row][(col + 1) % columns]
|
||||
listOf(
|
||||
Face(a, b, c),
|
||||
Face(a, c, d)
|
||||
)
|
||||
}
|
||||
}
|
||||
return Mesh(faces)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.kjs
|
||||
|
||||
import org.khronos.webgl.Int32Array
|
||||
import org.teavm.samples.software3d.geometry.Matrix
|
||||
import org.teavm.samples.software3d.rasterization.Raster
|
||||
import org.teavm.samples.software3d.rendering.Renderer
|
||||
import org.teavm.samples.software3d.scenes.geometry
|
||||
import org.w3c.dom.DedicatedWorkerGlobalScope
|
||||
|
||||
fun main() {
|
||||
self.onmessage = { event ->
|
||||
val data = event.data.asDynamic()
|
||||
when (data.type as String) {
|
||||
"init" -> init(data)
|
||||
"frame" -> renderFrame(data)
|
||||
"stop" -> self.close()
|
||||
}
|
||||
|
||||
Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun init(data: dynamic) {
|
||||
val width = data.width as Int
|
||||
val height = data.height as Int
|
||||
val step = data.step as Int
|
||||
val offset = data.offset as Int
|
||||
val (scene, updaterF) = geometry()
|
||||
raster = Raster(width, height / step)
|
||||
updater = updaterF
|
||||
renderer = Renderer(scene, raster, offset, step).apply {
|
||||
projection = Matrix.projection(-1f, 1f, -1f, 1f, 2f, 10f)
|
||||
viewport = Matrix.translation(width / 2f, height / 2f, 0f) *
|
||||
Matrix.scale(width / 2f, width / 2f, 1f)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderFrame(data: dynamic) {
|
||||
val time = data.time as Double
|
||||
val perfStart = self.performance.now()
|
||||
updater(time)
|
||||
renderer.render()
|
||||
val perfEnd = self.performance.now()
|
||||
val typedArray = raster.flip().asDynamic() as Int32Array
|
||||
val message = Any().asDynamic()
|
||||
message.data = typedArray.buffer
|
||||
message.time = ((perfEnd - perfStart) * 1000000).toInt()
|
||||
self.postMessage(message, arrayOf(typedArray.buffer))
|
||||
}
|
||||
|
||||
private val self = js("self") as DedicatedWorkerGlobalScope
|
||||
|
||||
private lateinit var raster: Raster
|
||||
private lateinit var renderer: Renderer
|
||||
private lateinit var updater: (Double) -> Unit
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.swing
|
||||
|
||||
import org.teavm.samples.software3d.geometry.Matrix
|
||||
import org.teavm.samples.software3d.rasterization.Raster
|
||||
import org.teavm.samples.software3d.rendering.Renderer
|
||||
import org.teavm.samples.software3d.scenes.geometry
|
||||
import java.awt.*
|
||||
import java.awt.image.BufferedImage
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.WindowConstants
|
||||
|
||||
private const val SCENE_WIDTH = 1024
|
||||
private const val SCENE_HEIGHT = 768
|
||||
|
||||
fun main() {
|
||||
val image = BufferedImage(SCENE_WIDTH, SCENE_HEIGHT, BufferedImage.TYPE_INT_ARGB)
|
||||
val imageComponent = ImageComponent(image)
|
||||
|
||||
val window = JFrame().apply {
|
||||
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
|
||||
add(imageComponent)
|
||||
pack()
|
||||
isVisible = true
|
||||
}
|
||||
|
||||
EventQueue.invokeAndWait {
|
||||
window.pack()
|
||||
}
|
||||
|
||||
val (scene, updater) = geometry()
|
||||
val taskCount = Runtime.getRuntime().availableProcessors()
|
||||
println("Running on $taskCount CPUs")
|
||||
val rasters = (0 until taskCount).map { Raster(SCENE_WIDTH, SCENE_HEIGHT / taskCount) }
|
||||
val renderers = rasters.mapIndexed() { index, raster ->
|
||||
Renderer(scene, raster, index, taskCount).apply {
|
||||
projection = Matrix.projection(-1f, 1f, -1f, 1f, 2f, 10f)
|
||||
viewport = Matrix.translation(SCENE_WIDTH / 2f, SCENE_HEIGHT / 2f, 0f) *
|
||||
Matrix.scale(SCENE_WIDTH / 2f, SCENE_WIDTH / 2f, 1f)
|
||||
}
|
||||
}
|
||||
|
||||
val queues = renderers.map { renderer ->
|
||||
val queue = LinkedBlockingQueue<CountDownLatch>()
|
||||
Thread {
|
||||
while (true) {
|
||||
val latch = queue.take()
|
||||
renderer.render()
|
||||
latch.countDown()
|
||||
}
|
||||
}.apply {
|
||||
isDaemon = true
|
||||
start()
|
||||
}
|
||||
queue
|
||||
}
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
var totalTime = 0L
|
||||
var totalTimeSec = 0L
|
||||
var frames = 1
|
||||
while (true) {
|
||||
val frameStart = System.currentTimeMillis()
|
||||
val frameStartPerf = System.nanoTime()
|
||||
val currentTime = frameStart - startTime
|
||||
updater(currentTime / 1000.0)
|
||||
val latch = CountDownLatch(taskCount)
|
||||
queues.forEach { it.offer(latch) }
|
||||
latch.await()
|
||||
val frameEndPerf = System.nanoTime()
|
||||
totalTime += (frameEndPerf - frameStartPerf) / 1000
|
||||
++frames
|
||||
if (totalTime / 1000000 != totalTimeSec) {
|
||||
totalTimeSec = totalTime / 1000000
|
||||
println("Average render time ${totalTime / frames}")
|
||||
}
|
||||
EventQueue.invokeAndWait {
|
||||
for (y in 0 until SCENE_HEIGHT) {
|
||||
val raster = rasters[y % taskCount]
|
||||
val start = raster.pointer(0, y / taskCount)
|
||||
image.setRGB(0, y, SCENE_WIDTH, 1, raster.color, start, SCENE_WIDTH)
|
||||
}
|
||||
imageComponent.repaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ImageComponent(private val image: BufferedImage) : JComponent() {
|
||||
override fun paint(g: Graphics) {
|
||||
g as Graphics2D
|
||||
g.drawImage(image as Image, 0, 0, null)
|
||||
}
|
||||
|
||||
override fun getPreferredSize(): Dimension = Dimension(SCENE_WIDTH, SCENE_HEIGHT)
|
||||
|
||||
override fun getMinimumSize(): Dimension = getPreferredSize()
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.teavm
|
||||
|
||||
import org.teavm.jso.JSObject
|
||||
import org.teavm.jso.browser.Window
|
||||
import org.teavm.jso.canvas.CanvasRenderingContext2D
|
||||
import org.teavm.jso.canvas.ImageData
|
||||
import org.teavm.jso.core.JSMapLike
|
||||
import org.teavm.jso.core.JSNumber
|
||||
import org.teavm.jso.core.JSObjects
|
||||
import org.teavm.jso.core.JSString
|
||||
import org.teavm.jso.dom.html.HTMLCanvasElement
|
||||
import org.teavm.jso.dom.html.HTMLDocument
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer
|
||||
import org.teavm.jso.typedarrays.Uint8ClampedArray
|
||||
import org.teavm.jso.workers.Worker
|
||||
import org.teavm.samples.software3d.util.PerformanceMeasure
|
||||
|
||||
class Controller(
|
||||
private val width: Int,
|
||||
private val height: Int,
|
||||
private val onPerformance: (Int, Long) -> Unit,
|
||||
) {
|
||||
private val canvas = HTMLDocument.current().getElementById("canvas") as HTMLCanvasElement
|
||||
private val context = canvas.getContext("2d") as CanvasRenderingContext2D
|
||||
private val startTime = System.currentTimeMillis()
|
||||
private var onRenderComplete: (Int, ArrayBuffer) -> Unit = { _, _ -> }
|
||||
private var workers: List<Worker> = emptyList()
|
||||
private var performanceMeasureByWorker: List<PerformanceMeasure> = emptyList()
|
||||
private val performanceMeasure = PerformanceMeasure(100000) { onPerformance(-1, it) }
|
||||
|
||||
val tasks: Int get() = workers.size
|
||||
|
||||
fun startRendering(tasks: Int, workerType: WorkerType) {
|
||||
performanceMeasure.reset()
|
||||
stopRendering()
|
||||
val scriptName = when (workerType) {
|
||||
WorkerType.JS -> "js-worker.js"
|
||||
WorkerType.WEBASSEMBLY -> "wasm-worker.js"
|
||||
WorkerType.KOTLIN_JS -> "kjs/software3d.js"
|
||||
}
|
||||
workers = (0 until tasks).map { index ->
|
||||
Worker.create(scriptName).apply {
|
||||
postMessage(JSObjects.createWithoutProto<JSMapLike<JSObject>>().apply {
|
||||
set("type", JSString.valueOf("init"))
|
||||
set("width", JSNumber.valueOf(width))
|
||||
set("height", JSNumber.valueOf(height))
|
||||
set("step", JSNumber.valueOf(tasks))
|
||||
set("offset", JSNumber.valueOf(index))
|
||||
})
|
||||
onMessage { event ->
|
||||
if (index < workers.size && workers[index] == event.target) {
|
||||
val data = event.data as JSMapLike<*>
|
||||
val buffer = data["data"] as ArrayBuffer
|
||||
onRenderComplete(index, buffer)
|
||||
performanceMeasureByWorker[index].reportFrame((data["time"] as JSNumber).intValue().toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
performanceMeasureByWorker = (0 until tasks).map { index ->
|
||||
PerformanceMeasure(100000) { onPerformance(index, it) }
|
||||
}
|
||||
renderFrame()
|
||||
}
|
||||
|
||||
private fun stopRendering() {
|
||||
workers.forEach {
|
||||
it.postMessage(JSObjects.createWithoutProto<JSMapLike<JSObject>>().apply {
|
||||
set("type", JSString.valueOf("stop"))
|
||||
})
|
||||
}
|
||||
workers = emptyList()
|
||||
}
|
||||
|
||||
private fun renderFrame() {
|
||||
val currenTime = System.currentTimeMillis()
|
||||
val frameTime = (currenTime - startTime) / 1000.0
|
||||
var pending = workers.size
|
||||
val buffers = arrayOfNulls<ArrayBuffer>(workers.size)
|
||||
performanceMeasure.startFrame()
|
||||
|
||||
for (worker in workers) {
|
||||
worker.postMessage(JSObjects.createWithoutProto<JSMapLike<JSObject>>().apply {
|
||||
set("type", JSString.valueOf("frame"))
|
||||
set("time", JSNumber.valueOf(frameTime))
|
||||
})
|
||||
}
|
||||
onRenderComplete = { index, buffer ->
|
||||
buffers[index] = buffer
|
||||
if (--pending == 0) {
|
||||
performanceMeasure.endFrame()
|
||||
Window.requestAnimationFrame {
|
||||
displayBuffers(buffers)
|
||||
renderFrame()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayBuffers(buffers: Array<out ArrayBuffer?>) {
|
||||
for (y in 0 until height) {
|
||||
val buffer = buffers[y % buffers.size]!!
|
||||
val array = Uint8ClampedArray.create(buffer, width * 4 * (y / buffers.size), width * 4)
|
||||
val imageData = ImageData.create(array, width, 1)
|
||||
context.putImageData(imageData, 0.0, y.toDouble())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.teavm
|
||||
|
||||
import org.teavm.interop.Address
|
||||
import org.teavm.interop.Export
|
||||
import org.teavm.interop.Import
|
||||
import org.teavm.samples.software3d.geometry.Matrix
|
||||
import org.teavm.samples.software3d.rasterization.Raster
|
||||
import org.teavm.samples.software3d.rendering.Renderer
|
||||
import org.teavm.samples.software3d.scenes.geometry
|
||||
|
||||
fun main() {
|
||||
println("Worker started")
|
||||
}
|
||||
|
||||
@Export(name = "initWorker")
|
||||
fun init(width: Int, height: Int, step: Int, offset: Int) {
|
||||
val (scene, updaterF) = geometry()
|
||||
raster = Raster(width, height / step)
|
||||
updater = updaterF
|
||||
renderer = Renderer(scene, raster, offset, step).apply {
|
||||
projection = Matrix.projection(-1f, 1f, -1f, 1f, 2f, 10f)
|
||||
viewport = Matrix.translation(width / 2f, height / 2f, 0f) *
|
||||
Matrix.scale(width / 2f, width / 2f, 1f)
|
||||
}
|
||||
println("Worker initialized")
|
||||
}
|
||||
|
||||
@Export(name = "renderFrame")
|
||||
fun renderFrame(time: Double) {
|
||||
val perfStart = System.nanoTime()
|
||||
updater(time)
|
||||
renderer.render()
|
||||
val perfEnd = System.nanoTime()
|
||||
val buffer = raster.color
|
||||
sendRenderResult(Address.ofData(buffer), buffer.size * 4, (perfEnd - perfStart).toInt())
|
||||
}
|
||||
|
||||
@Import(module = "renderer", name = "result")
|
||||
external fun sendRenderResult(data: Address, dataSize: Int, time: Int)
|
||||
|
||||
private lateinit var raster: Raster
|
||||
private lateinit var renderer: Renderer
|
||||
private lateinit var updater: (Double) -> Unit
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.teavm
|
||||
|
||||
import org.teavm.jso.JSObject
|
||||
import org.teavm.jso.browser.Window
|
||||
import org.teavm.jso.core.*
|
||||
import org.teavm.samples.software3d.geometry.Matrix
|
||||
import org.teavm.samples.software3d.rasterization.Raster
|
||||
import org.teavm.samples.software3d.rendering.Renderer
|
||||
import org.teavm.samples.software3d.scenes.geometry
|
||||
|
||||
fun worker() {
|
||||
val worker = RenderWorker()
|
||||
Window.worker().listenMessage {
|
||||
val dataJson = it.data as JSMapLike<*>
|
||||
when ((dataJson["type"] as JSString).stringValue()) {
|
||||
"init" -> worker.init(dataJson)
|
||||
"frame" -> worker.renderFrame(dataJson)
|
||||
"stop" -> Window.worker().close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RenderWorker {
|
||||
private lateinit var raster: Raster
|
||||
private lateinit var renderer: Renderer
|
||||
private lateinit var updater: (Double) -> Unit
|
||||
private var width: Int = 0
|
||||
private var height: Int = 0
|
||||
|
||||
fun init(params: JSMapLike<*>) {
|
||||
val (scene, updaterF) = geometry()
|
||||
width = (params["width"] as JSNumber).intValue()
|
||||
height = (params["height"] as JSNumber).intValue()
|
||||
val step = (params["step"] as JSNumber).intValue()
|
||||
val offset = (params["offset"] as JSNumber).intValue()
|
||||
raster = Raster(width, height / step)
|
||||
updater = updaterF
|
||||
renderer = Renderer(scene, raster, offset, step).apply {
|
||||
projection = Matrix.projection(-1f, 1f, -1f, 1f, 2f, 10f)
|
||||
viewport = Matrix.translation(width / 2f, height / 2f, 0f) *
|
||||
Matrix.scale(width / 2f, width / 2f, 1f)
|
||||
}
|
||||
}
|
||||
|
||||
fun renderFrame(params: JSMapLike<*>) {
|
||||
val time = (params["time"] as JSNumber).doubleValue()
|
||||
val perfStart = System.nanoTime()
|
||||
updater(time)
|
||||
renderer.render()
|
||||
val perfEnd = System.nanoTime()
|
||||
val buffer = extractBuffer(raster.flip())
|
||||
postMessageFromWorker(JSObjects.createWithoutProto<JSMapLike<JSObject>>().apply {
|
||||
set("data", buffer)
|
||||
set("time", JSNumber.valueOf((perfEnd - perfStart).toInt()))
|
||||
}, JSArray.of(buffer))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.teavm
|
||||
|
||||
enum class WorkerType {
|
||||
JS,
|
||||
WEBASSEMBLY,
|
||||
KOTLIN_JS
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.teavm
|
||||
|
||||
import org.teavm.jso.dom.html.HTMLDocument
|
||||
import org.teavm.jso.dom.html.HTMLElement
|
||||
import org.teavm.jso.dom.html.HTMLOptionElement
|
||||
import org.teavm.jso.dom.html.HTMLSelectElement
|
||||
|
||||
private const val SCENE_WIDTH = 1024
|
||||
private const val SCENE_HEIGHT = 768
|
||||
|
||||
fun main(args: Array<out String>) {
|
||||
if (args.size == 1 && args[0] == "worker") {
|
||||
worker()
|
||||
} else {
|
||||
runController()
|
||||
}
|
||||
}
|
||||
|
||||
fun runController() {
|
||||
val performanceIndicator = HTMLDocument.current().getElementById("performance-indicator")
|
||||
var performanceIndicatorByWorkers: List<HTMLElement> = emptyList()
|
||||
val maxWorkers = cpuCount()
|
||||
var workerType = WorkerType.JS
|
||||
val controller = Controller(SCENE_WIDTH, SCENE_HEIGHT) { index, value ->
|
||||
if (index == -1) {
|
||||
performanceIndicator.innerText = value.toString()
|
||||
} else {
|
||||
performanceIndicatorByWorkers.getOrNull(index)?.let {
|
||||
it.innerText = value.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
performanceIndicatorByWorkers = recreatePerformanceIndicators(maxWorkers)
|
||||
controller.startRendering(maxWorkers, workerType)
|
||||
|
||||
val cpuSelector = HTMLDocument.current().getElementById("workers") as HTMLSelectElement
|
||||
for (i in 1..maxWorkers) {
|
||||
val option = HTMLDocument.current().createElement("option") as HTMLOptionElement
|
||||
option.value = i.toString()
|
||||
option.text = i.toString()
|
||||
cpuSelector.appendChild(option)
|
||||
}
|
||||
cpuSelector.value = maxWorkers.toString()
|
||||
cpuSelector.addEventListener("change") {
|
||||
val newValue = cpuSelector.value.toInt()
|
||||
if (controller.tasks != newValue) {
|
||||
controller.startRendering(newValue, workerType)
|
||||
performanceIndicatorByWorkers = recreatePerformanceIndicators(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
val workerSelector = HTMLDocument.current().getElementById("worker-type") as HTMLSelectElement
|
||||
workerSelector.addEventListener("change") {
|
||||
val newValue = when (workerSelector.value) {
|
||||
"webassembly" -> WorkerType.WEBASSEMBLY
|
||||
"kjs" -> WorkerType.KOTLIN_JS
|
||||
else -> WorkerType.JS
|
||||
}
|
||||
if (workerType != newValue) {
|
||||
workerType = newValue
|
||||
controller.startRendering(controller.tasks, workerType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recreatePerformanceIndicators(count: Int): List<HTMLElement> {
|
||||
val container = HTMLDocument.current().getElementById("performance-indicators-by-workers")
|
||||
while (container.hasChildNodes()) {
|
||||
container.removeChild(container.firstChild)
|
||||
}
|
||||
return (0 until count).map {
|
||||
HTMLDocument.current().createElement("li").also {
|
||||
container.appendChild(it)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.teavm
|
||||
|
||||
import org.teavm.jso.JSBody
|
||||
import org.teavm.jso.JSByRef
|
||||
import org.teavm.jso.JSObject
|
||||
import org.teavm.jso.core.JSArray
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer
|
||||
|
||||
@JSBody(params = ["data"], script = "return data.buffer;")
|
||||
external fun extractBuffer(@JSByRef data: IntArray): ArrayBuffer
|
||||
|
||||
@JSBody(params = ["message", "transferable"], script = "self.postMessage(message, transferable);")
|
||||
external fun postMessageFromWorker(message: JSObject, transferable: JSArray<out JSObject>)
|
||||
|
||||
@JSBody(script = "return navigator.hardwareConcurrency;")
|
||||
external fun cpuCount(): Int
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.teavm.samples.software3d.util
|
||||
|
||||
class PerformanceMeasure(private val measureInterval: Long, private val update: (Long) -> Unit) {
|
||||
private var totalPerfTime = 0L
|
||||
private var totalPertTimeSecond = 0L
|
||||
private var frameCount = 0
|
||||
private var startPerfTime = 0L
|
||||
|
||||
fun reset() {
|
||||
totalPerfTime = 0L
|
||||
totalPertTimeSecond = 0L
|
||||
frameCount = 0
|
||||
}
|
||||
|
||||
fun startFrame() {
|
||||
startPerfTime = System.nanoTime()
|
||||
}
|
||||
|
||||
fun endFrame() {
|
||||
val endPerfTime = System.nanoTime()
|
||||
reportFrame(endPerfTime - startPerfTime)
|
||||
}
|
||||
|
||||
fun reportFrame(frameTime: Long) {
|
||||
totalPerfTime += frameTime / 1000
|
||||
frameCount++
|
||||
if (totalPerfTime / measureInterval != totalPertTimeSecond) {
|
||||
totalPertTimeSecond = totalPerfTime / measureInterval
|
||||
update(totalPerfTime / frameCount)
|
||||
}
|
||||
}
|
||||
}
|
37
samples/software3d/src/main/webapp/index.html
Normal file
37
samples/software3d/src/main/webapp/index.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
<!--
|
||||
Copyright 2014 Alexey Andreev.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Software 3D renderer</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
<script type="text/javascript" charset="utf-8" src="js/software3d.js"></script>
|
||||
</head>
|
||||
<body onload="main()">
|
||||
<div>
|
||||
<canvas id="canvas" width="1024" height="768"></canvas>
|
||||
</div>
|
||||
<div><label for="worker-type">Worker type:</label> <select id="worker-type">
|
||||
<option value="js">TeaVM JS</option>
|
||||
<option value="webassembly">TeaVM WebAssembly</option>
|
||||
<option value="kjs">Kotlin/JS</option>
|
||||
</select></div>
|
||||
<div><label for="workers">Workers:</label> <select id="workers"></select></div>
|
||||
<div>Average frame rendering time, microseconds: <span id="performance-indicator"></span></div>
|
||||
<div>By worker:</div>
|
||||
<ol id="performance-indicators-by-workers"></ol>
|
||||
</body>
|
||||
</html>
|
18
samples/software3d/src/main/webapp/js-worker.js
Normal file
18
samples/software3d/src/main/webapp/js-worker.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
importScripts("js/software3d.js");
|
||||
main(["worker"])
|
67
samples/software3d/src/main/webapp/wasm-worker.js
Normal file
67
samples/software3d/src/main/webapp/wasm-worker.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
importScripts("wasm/software3d.wasm-runtime.js");
|
||||
|
||||
let instance = null;
|
||||
let pendingInstanceFunctions = [];
|
||||
|
||||
addEventListener("message", e => {
|
||||
let data = e.data
|
||||
switch (data.type) {
|
||||
case "init":
|
||||
pendingInstanceFunctions.push(() => {
|
||||
instance.exports.initWorker(data.width, data.height, data.step, data.offset)
|
||||
});
|
||||
runPendingFunctions();
|
||||
break;
|
||||
case "frame":
|
||||
pendingInstanceFunctions.push(() => {
|
||||
instance.exports.renderFrame(data.time);
|
||||
});
|
||||
runPendingFunctions();
|
||||
break;
|
||||
case "stop":
|
||||
self.close();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
TeaVM.wasm.load("wasm/software3d.wasm", {
|
||||
installImports(o, controller) {
|
||||
o.renderer = {
|
||||
result(data, size, time) {
|
||||
let buffer = controller.instance.exports.memory.buffer.slice(data, data + size);
|
||||
self.postMessage({ data: buffer, time: time });
|
||||
}
|
||||
}
|
||||
},
|
||||
}).then(teavm => {
|
||||
teavm.main([]);
|
||||
instance = teavm.instance;
|
||||
runPendingFunctions();
|
||||
});
|
||||
|
||||
|
||||
function runPendingFunctions() {
|
||||
if (instance === null) {
|
||||
return;
|
||||
}
|
||||
for (let f of pendingInstanceFunctions) {
|
||||
f();
|
||||
}
|
||||
pendingInstanceFunctions = [];
|
||||
}
|
Loading…
Reference in New Issue
Block a user