mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
samples: add software 3D renderer in Kotlin
This commit is contained in:
parent
932f33ae2c
commit
01cf27b3d8
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.8.0"
|
kotlin("multiplatform") version "1.9.20"
|
||||||
war
|
war
|
||||||
id("org.teavm")
|
id("org.teavm")
|
||||||
}
|
}
|
||||||
|
@ -28,3 +28,7 @@ teavm.js {
|
||||||
addedToWebApp = true
|
addedToWebApp = true
|
||||||
mainClass = "org.teavm.samples.kotlin.HelloKt"
|
mainClass = "org.teavm.samples.kotlin.HelloKt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm()
|
||||||
|
}
|
|
@ -59,6 +59,7 @@ include("pi")
|
||||||
include("kotlin")
|
include("kotlin")
|
||||||
include("scala")
|
include("scala")
|
||||||
include("web-apis")
|
include("web-apis")
|
||||||
|
include("software3d")
|
||||||
|
|
||||||
gradle.allprojects {
|
gradle.allprojects {
|
||||||
apply<WarPlugin>()
|
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