mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
Upgraded to three.js 0.126.0 and improved performance of quest renderer.
This commit is contained in:
parent
e6a7a5c3ed
commit
f57de99577
@ -42,7 +42,7 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.1.1")
|
||||
implementation(npm("golden-layout", "^1.5.9"))
|
||||
implementation(npm("monaco-editor", "0.20.0"))
|
||||
implementation(npm("three", "^0.122.0"))
|
||||
implementation(npm("three", "^0.126.0"))
|
||||
implementation(npm("javascript-lp-solver", "0.4.17"))
|
||||
|
||||
implementation(devNpm("file-loader", "^6.0.0"))
|
||||
|
@ -77,6 +77,7 @@ private fun createThreeRenderer(canvas: HTMLCanvasElement): DisposableThreeRende
|
||||
})
|
||||
|
||||
init {
|
||||
renderer.debug.checkShaderErrors = false
|
||||
renderer.setPixelRatio(window.devicePixelRatio)
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,8 @@ package world.phantasmal.web.core.rendering
|
||||
import world.phantasmal.web.externals.three.Object3D
|
||||
|
||||
/**
|
||||
* Recursively disposes any geometries/materials/textures/skeletons attached to the given [Object3D]
|
||||
* and its children.
|
||||
* Recursively disposes the given object and any geometries/materials/textures/skeletons attached to
|
||||
* it and its children.
|
||||
*/
|
||||
fun disposeObject3DResources(obj: Object3D) {
|
||||
val dynObj = obj.asDynamic()
|
||||
@ -22,6 +22,10 @@ fun disposeObject3DResources(obj: Object3D) {
|
||||
dynObj.material.dispose()
|
||||
}
|
||||
|
||||
if (dynObj.dispose != null) {
|
||||
dynObj.dispose()
|
||||
}
|
||||
|
||||
for (child in obj.children) {
|
||||
disposeObject3DResources(child)
|
||||
}
|
||||
|
@ -125,7 +125,6 @@ class MeshBuilder {
|
||||
check(positions.size == normals.size)
|
||||
check(uvs.isEmpty() || positions.size == uvs.size)
|
||||
|
||||
// Per-buffer attributes.
|
||||
val positions = Float32Array(3 * positions.size)
|
||||
val normals = Float32Array(3 * normals.size)
|
||||
val uvs = if (uvs.isEmpty()) null else Float32Array(2 * uvs.size)
|
||||
@ -153,9 +152,6 @@ class MeshBuilder {
|
||||
geom.setAttribute("normal", Float32BufferAttribute(normals, 3))
|
||||
uvs?.let { geom.setAttribute("uv", Float32BufferAttribute(uvs, 2)) }
|
||||
|
||||
// Per group/material attributes.
|
||||
val indices = Uint16Array(indexCount)
|
||||
|
||||
if (skinning) {
|
||||
check(this.positions.size == boneIndices.size / 4)
|
||||
check(this.positions.size == boneWeights.size / 4)
|
||||
@ -174,6 +170,8 @@ class MeshBuilder {
|
||||
)
|
||||
}
|
||||
|
||||
val indices = Uint16Array(indexCount)
|
||||
|
||||
var offset = 0
|
||||
val texCache = mutableMapOf<Int, Texture?>()
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
package world.phantasmal.web.externals.three
|
||||
|
||||
import org.khronos.webgl.Float32Array
|
||||
import org.khronos.webgl.Int32Array
|
||||
import org.khronos.webgl.Uint16Array
|
||||
import org.w3c.dom.HTMLCanvasElement
|
||||
|
||||
@ -123,7 +124,7 @@ external class Quaternion(
|
||||
/**
|
||||
* Inverts this quaternion.
|
||||
*/
|
||||
fun inverse(): Quaternion
|
||||
fun invert(): Quaternion
|
||||
|
||||
/**
|
||||
* Multiplies this quaternion by [q].
|
||||
@ -218,6 +219,7 @@ open external class WebGLRenderer(
|
||||
override val domElement: HTMLCanvasElement
|
||||
|
||||
var autoClearColor: Boolean
|
||||
var debug: WebGLDebug
|
||||
|
||||
override fun render(scene: Object3D, camera: Camera)
|
||||
|
||||
@ -232,6 +234,10 @@ open external class WebGLRenderer(
|
||||
fun dispose()
|
||||
}
|
||||
|
||||
external interface WebGLDebug {
|
||||
var checkShaderErrors: Boolean
|
||||
}
|
||||
|
||||
open external class Object3D {
|
||||
/**
|
||||
* Optional name of the object (doesn't need to be unique).
|
||||
@ -299,47 +305,25 @@ open external class Object3D {
|
||||
external class Group : Object3D
|
||||
|
||||
open external class Mesh(
|
||||
geometry: Geometry = definedExternally,
|
||||
geometry: BufferGeometry = definedExternally,
|
||||
material: Material = definedExternally,
|
||||
) : Object3D {
|
||||
constructor(
|
||||
geometry: Geometry,
|
||||
material: Array<Material>,
|
||||
)
|
||||
|
||||
constructor(
|
||||
geometry: BufferGeometry = definedExternally,
|
||||
material: Material = definedExternally,
|
||||
)
|
||||
|
||||
constructor(
|
||||
geometry: BufferGeometry,
|
||||
material: Array<Material>,
|
||||
)
|
||||
|
||||
var geometry: Any /* Geometry | BufferGeometry */
|
||||
var geometry: BufferGeometry
|
||||
var material: Any /* Material | Material[] */
|
||||
|
||||
fun translateY(distance: Double): Mesh
|
||||
}
|
||||
|
||||
external class SkinnedMesh(
|
||||
geometry: Geometry = definedExternally,
|
||||
geometry: BufferGeometry = definedExternally,
|
||||
material: Material = definedExternally,
|
||||
useVertexTexture: Boolean = definedExternally,
|
||||
) : Mesh {
|
||||
constructor(
|
||||
geometry: Geometry,
|
||||
material: Array<Material>,
|
||||
useVertexTexture: Boolean = definedExternally,
|
||||
)
|
||||
|
||||
constructor(
|
||||
geometry: BufferGeometry = definedExternally,
|
||||
material: Material = definedExternally,
|
||||
useVertexTexture: Boolean = definedExternally,
|
||||
)
|
||||
|
||||
constructor(
|
||||
geometry: BufferGeometry,
|
||||
material: Array<Material>,
|
||||
@ -352,22 +336,10 @@ external class SkinnedMesh(
|
||||
}
|
||||
|
||||
external class InstancedMesh(
|
||||
geometry: Geometry,
|
||||
geometry: BufferGeometry,
|
||||
material: Material,
|
||||
count: Int,
|
||||
) : Mesh {
|
||||
constructor(
|
||||
geometry: Geometry,
|
||||
material: Array<Material>,
|
||||
count: Int,
|
||||
)
|
||||
|
||||
constructor(
|
||||
geometry: BufferGeometry,
|
||||
material: Material,
|
||||
count: Int,
|
||||
)
|
||||
|
||||
constructor(
|
||||
geometry: BufferGeometry,
|
||||
material: Array<Material>,
|
||||
@ -379,6 +351,7 @@ external class InstancedMesh(
|
||||
|
||||
fun getMatrixAt(index: Int, matrix: Matrix4)
|
||||
fun setMatrixAt(index: Int, matrix: Matrix4)
|
||||
fun dispose()
|
||||
}
|
||||
|
||||
external class Bone : Object3D {
|
||||
@ -502,53 +475,6 @@ external class Color() {
|
||||
fun setHSL(h: Double, s: Double, l: Double): Color
|
||||
}
|
||||
|
||||
open external class Geometry : EventDispatcher {
|
||||
var boundingBox: Box3?
|
||||
var boundingSphere: Sphere?
|
||||
|
||||
/**
|
||||
* The array of vertices hold every position of points of the model.
|
||||
* To signal an update in this array, Geometry.verticesNeedUpdate needs to be set to true.
|
||||
*/
|
||||
var vertices: Array<Vector3>
|
||||
|
||||
/**
|
||||
* Array of triangles or/and quads.
|
||||
* The array of faces describe how each vertex in the model is connected with each other.
|
||||
* To signal an update in this array, Geometry.elementsNeedUpdate needs to be set to true.
|
||||
*/
|
||||
var faces: Array<Face3>
|
||||
|
||||
/**
|
||||
* Array of face UV layers.
|
||||
* Each UV layer is an array of UV matching order and number of vertices in faces.
|
||||
* To signal an update in this array, Geometry.uvsNeedUpdate needs to be set to true.
|
||||
*/
|
||||
var faceVertexUvs: Array<Array<Array<Vector2>>>
|
||||
|
||||
fun translate(x: Double, y: Double, z: Double): Geometry
|
||||
|
||||
/**
|
||||
* Computes bounding box of the geometry, updating {@link Geometry.boundingBox} attribute.
|
||||
*/
|
||||
fun computeBoundingBox()
|
||||
|
||||
/**
|
||||
* Computes bounding sphere of the geometry, updating Geometry.boundingSphere attribute.
|
||||
* Neither bounding boxes or bounding spheres are computed by default. They need to be explicitly computed, otherwise they are null.
|
||||
*/
|
||||
fun computeBoundingSphere()
|
||||
|
||||
fun dispose()
|
||||
}
|
||||
|
||||
external class PlaneGeometry(
|
||||
width: Double = definedExternally,
|
||||
height: Double = definedExternally,
|
||||
widthSegments: Double = definedExternally,
|
||||
heightSegments: Double = definedExternally,
|
||||
) : Geometry
|
||||
|
||||
open external class BufferGeometry : EventDispatcher {
|
||||
var boundingBox: Box3?
|
||||
var boundingSphere: Sphere?
|
||||
@ -569,6 +495,13 @@ open external class BufferGeometry : EventDispatcher {
|
||||
fun dispose()
|
||||
}
|
||||
|
||||
external class PlaneGeometry(
|
||||
width: Double = definedExternally,
|
||||
height: Double = definedExternally,
|
||||
widthSegments: Double = definedExternally,
|
||||
heightSegments: Double = definedExternally,
|
||||
) : BufferGeometry
|
||||
|
||||
external class CylinderBufferGeometry(
|
||||
radiusTop: Double = definedExternally,
|
||||
radiusBottom: Double = definedExternally,
|
||||
@ -586,6 +519,12 @@ open external class BufferAttribute {
|
||||
fun copyAt(index1: Int, bufferAttribute: BufferAttribute, index2: Int): BufferAttribute
|
||||
}
|
||||
|
||||
external class Int32BufferAttribute(
|
||||
array: Int32Array,
|
||||
itemSize: Int,
|
||||
normalize: Boolean = definedExternally,
|
||||
) : BufferAttribute
|
||||
|
||||
external class Uint16BufferAttribute(
|
||||
array: Uint16Array,
|
||||
itemSize: Int,
|
||||
|
@ -1,7 +1,12 @@
|
||||
package world.phantasmal.web.questEditor.loading
|
||||
|
||||
import org.khronos.webgl.ArrayBuffer
|
||||
import org.khronos.webgl.Float32Array
|
||||
import org.khronos.webgl.Uint16Array
|
||||
import world.phantasmal.core.JsArray
|
||||
import world.phantasmal.core.asArray
|
||||
import world.phantasmal.core.asJsArray
|
||||
import world.phantasmal.core.jsArrayOf
|
||||
import world.phantasmal.lib.Endianness
|
||||
import world.phantasmal.lib.Episode
|
||||
import world.phantasmal.lib.cursor.cursor
|
||||
@ -81,8 +86,7 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
|
||||
private fun addSectionsToCollisionGeometry(collisionGeom: Object3D, renderGeom: Object3D) {
|
||||
for (collisionArea in collisionGeom.children) {
|
||||
val origin =
|
||||
((collisionArea as Mesh).geometry as Geometry).boundingBox!!.getCenter(tmpVec)
|
||||
val origin = ((collisionArea as Mesh).geometry).boundingBox!!.getCenter(tmpVec)
|
||||
|
||||
// Cast a ray downward from the center of the section.
|
||||
raycaster.set(origin, DOWN)
|
||||
@ -319,16 +323,14 @@ private fun areaCollisionGeometryToObject3D(
|
||||
episode: Episode,
|
||||
areaVariant: AreaVariantModel,
|
||||
): Object3D {
|
||||
val obj3d = Group()
|
||||
obj3d.name = "Collision Geometry $episode-${areaVariant.area.id}-${areaVariant.id}"
|
||||
val group = Group()
|
||||
group.name = "Collision Geometry $episode-${areaVariant.area.id}-${areaVariant.id}"
|
||||
|
||||
for (collisionMesh in obj.meshes) {
|
||||
// Use Geometry instead of BufferGeometry for better raycaster performance.
|
||||
val geom = Geometry()
|
||||
|
||||
geom.vertices = Array(collisionMesh.vertices.size) {
|
||||
vec3ToThree(collisionMesh.vertices[it])
|
||||
}
|
||||
val positions = jsArrayOf<Float>()
|
||||
val normals = jsArrayOf<Float>()
|
||||
val materialGroups = mutableMapOf<Int, JsArray<Short>>()
|
||||
var index: Short = 0
|
||||
|
||||
for (triangle in collisionMesh.triangles) {
|
||||
val isSectionTransition = (triangle.flags and 0b1000000) != 0
|
||||
@ -343,29 +345,47 @@ private fun areaCollisionGeometryToObject3D(
|
||||
|
||||
// Filter out walls.
|
||||
if (materialIndex != 0) {
|
||||
geom.faces.asDynamic().push(
|
||||
Face3(
|
||||
triangle.index1,
|
||||
triangle.index2,
|
||||
triangle.index3,
|
||||
vec3ToThree(triangle.normal),
|
||||
materialIndex = materialIndex,
|
||||
)
|
||||
)
|
||||
val p1 = collisionMesh.vertices[triangle.index1]
|
||||
val p2 = collisionMesh.vertices[triangle.index2]
|
||||
val p3 = collisionMesh.vertices[triangle.index3]
|
||||
positions.push(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z)
|
||||
|
||||
val n = triangle.normal
|
||||
normals.push(n.x, n.y, n.z, n.x, n.y, n.z, n.x, n.y, n.z)
|
||||
|
||||
val indices = materialGroups.getOrPut(materialIndex) { jsArrayOf() }
|
||||
indices.push(index++, index++, index++)
|
||||
}
|
||||
}
|
||||
|
||||
if (geom.faces.isNotEmpty()) {
|
||||
if (index > 0) {
|
||||
val geom = BufferGeometry()
|
||||
geom.setAttribute(
|
||||
"position", Float32BufferAttribute(Float32Array(positions.asArray()), 3),
|
||||
)
|
||||
geom.setAttribute(
|
||||
"normal", Float32BufferAttribute(Float32Array(normals.asArray()), 3),
|
||||
)
|
||||
val indices = Uint16Array(index.toInt())
|
||||
var offset = 0
|
||||
|
||||
for ((materialIndex, vertexIndices) in materialGroups) {
|
||||
indices.set(vertexIndices.asArray(), offset)
|
||||
geom.addGroup(offset, vertexIndices.length, materialIndex)
|
||||
offset += vertexIndices.length
|
||||
}
|
||||
|
||||
geom.setIndex(Uint16BufferAttribute(indices, 1))
|
||||
geom.computeBoundingBox()
|
||||
geom.computeBoundingSphere()
|
||||
|
||||
obj3d.add(
|
||||
group.add(
|
||||
Mesh(geom, COLLISION_MATERIALS).apply {
|
||||
renderOrder = 1
|
||||
}
|
||||
)
|
||||
|
||||
obj3d.add(
|
||||
group.add(
|
||||
Mesh(geom, COLLISION_WIREFRAME_MATERIALS).apply {
|
||||
renderOrder = 2
|
||||
}
|
||||
@ -373,5 +393,5 @@ private fun areaCollisionGeometryToObject3D(
|
||||
}
|
||||
}
|
||||
|
||||
return obj3d
|
||||
return group
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ abstract class QuestEntityModel<Type : EntityType, Entity : QuestEntity<Type>>(
|
||||
} else {
|
||||
q1.setFromEuler(rot)
|
||||
q2.setFromEuler(section.rotation)
|
||||
q2.inverse()
|
||||
q2.invert()
|
||||
q1 *= q2
|
||||
floorModEuler(q1.toEuler())
|
||||
}
|
||||
|
@ -17,5 +17,5 @@ class SectionModel(
|
||||
}
|
||||
}
|
||||
|
||||
val inverseRotation: Euler = rotation.toQuaternion().inverse().toEuler()
|
||||
val inverseRotation: Euler = rotation.toQuaternion().invert().toEuler()
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class EntityImageRenderer(
|
||||
scene.add(light, mesh)
|
||||
|
||||
// Compute camera position.
|
||||
val bSphere = (mesh.geometry as BufferGeometry).boundingSphere!!
|
||||
val bSphere = mesh.geometry.boundingSphere!!
|
||||
camera.position.copy(cameraPos)
|
||||
camera.position *= bSphere.radius * cameraDistFactor
|
||||
camera.lookAt(bSphere.center)
|
||||
@ -69,7 +69,6 @@ class EntityImageRenderer(
|
||||
mesh.material = origMaterial
|
||||
threeRenderer.render(scene, camera)
|
||||
|
||||
threeRenderer.render(scene, camera)
|
||||
return threeRenderer.domElement.toDataURL()
|
||||
} finally {
|
||||
// Ensure we dispose the original material and not the background material.
|
||||
|
@ -1,5 +1,7 @@
|
||||
package world.phantasmal.web.questEditor.rendering
|
||||
|
||||
import world.phantasmal.core.disposable.TrackedDisposable
|
||||
import world.phantasmal.web.core.rendering.disposeObject3DResources
|
||||
import world.phantasmal.web.externals.three.InstancedMesh
|
||||
import world.phantasmal.web.questEditor.models.QuestEntityModel
|
||||
|
||||
@ -15,13 +17,18 @@ class EntityInstancedMesh(
|
||||
* [EntityInstancedMesh].
|
||||
*/
|
||||
private val modelChanged: (QuestEntityModel<*, *>) -> Unit,
|
||||
) {
|
||||
) : TrackedDisposable() {
|
||||
private val instances: MutableList<EntityInstance> = mutableListOf()
|
||||
|
||||
init {
|
||||
mesh.userData = this
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
disposeObject3DResources(mesh)
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
fun getInstance(entity: QuestEntityModel<*, *>): EntityInstance? =
|
||||
instances.find { it.entity == entity }
|
||||
|
||||
|
@ -36,7 +36,7 @@ class EntityMeshManager(
|
||||
add(entity)
|
||||
})
|
||||
},
|
||||
{ /* Nothing to dispose. */ },
|
||||
EntityInstancedMesh::dispose,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package world.phantasmal.web.questEditor.widgets
|
||||
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.coroutines.launch
|
||||
import org.w3c.dom.Node
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
@ -8,6 +9,7 @@ import world.phantasmal.core.math.degToRad
|
||||
import world.phantasmal.core.math.radToDeg
|
||||
import world.phantasmal.lib.fileFormats.quest.EntityPropType
|
||||
import world.phantasmal.observable.value.Val
|
||||
import world.phantasmal.observable.value.mutableVal
|
||||
import world.phantasmal.web.core.widgets.UnavailableWidget
|
||||
import world.phantasmal.web.questEditor.controllers.EntityInfoController
|
||||
import world.phantasmal.web.questEditor.models.QuestEntityPropModel
|
||||
@ -90,9 +92,21 @@ class EntityInfoWidget(private val ctrl: EntityInfoController) : Widget(enabled
|
||||
tr {
|
||||
className = COORD_CLASS
|
||||
|
||||
val inputValue = mutableVal(value.value)
|
||||
var timeout = -1
|
||||
|
||||
observe(value) {
|
||||
if (timeout == -1) {
|
||||
timeout = window.setTimeout({
|
||||
inputValue.value = value.value
|
||||
timeout = -1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val input = DoubleInput(
|
||||
enabled = ctrl.enabled,
|
||||
value = value,
|
||||
value = inputValue,
|
||||
onChange = onChange,
|
||||
label = label,
|
||||
roundTo = 3,
|
||||
|
@ -103,8 +103,7 @@ class MeshRenderer(
|
||||
|
||||
if (resetCamera) {
|
||||
// Compute camera position.
|
||||
val geom = mesh.geometry as BufferGeometry
|
||||
val bSphere = geom.boundingSphere!!
|
||||
val bSphere = mesh.geometry.boundingSphere!!
|
||||
val cameraDistFactor =
|
||||
1.5 / tan(degToRad((context.camera as PerspectiveCamera).fov) / 2)
|
||||
val cameraPos = CAMERA_POS * (bSphere.radius * cameraDistFactor)
|
||||
|
@ -1,6 +1,8 @@
|
||||
package world.phantasmal.web.viewer.rendering
|
||||
|
||||
import mu.KotlinLogging
|
||||
import org.khronos.webgl.Float32Array
|
||||
import org.khronos.webgl.Uint16Array
|
||||
import org.w3c.dom.HTMLCanvasElement
|
||||
import world.phantasmal.lib.fileFormats.ninja.XvrTexture
|
||||
import world.phantasmal.web.core.rendering.*
|
||||
@ -51,8 +53,8 @@ class TextureRenderer(
|
||||
|
||||
private fun texturesChanged(textures: List<XvrTexture>) {
|
||||
meshes.forEach { mesh ->
|
||||
disposeObject3DResources(mesh)
|
||||
context.scene.remove(mesh)
|
||||
disposeObject3DResources(mesh)
|
||||
}
|
||||
|
||||
inputManager.resetCamera()
|
||||
@ -109,21 +111,59 @@ class TextureRenderer(
|
||||
}
|
||||
}
|
||||
|
||||
private fun createQuad(x: Int, y: Int, width: Int, height: Int): PlaneGeometry {
|
||||
val quad = PlaneGeometry(
|
||||
width.toDouble(),
|
||||
height.toDouble(),
|
||||
widthSegments = 1.0,
|
||||
heightSegments = 1.0,
|
||||
private fun createQuad(x: Int, y: Int, width: Int, height: Int): BufferGeometry {
|
||||
val halfWidth = width / 2f
|
||||
val halfHeight = height / 2f
|
||||
|
||||
val geom = BufferGeometry()
|
||||
|
||||
geom.setAttribute(
|
||||
"position",
|
||||
Float32BufferAttribute(
|
||||
Float32Array(arrayOf(
|
||||
-halfWidth, -halfHeight, 0f,
|
||||
-halfWidth, halfHeight, 0f,
|
||||
halfWidth, halfHeight, 0f,
|
||||
halfWidth, -halfHeight, 0f,
|
||||
)),
|
||||
3,
|
||||
),
|
||||
)
|
||||
quad.faceVertexUvs = arrayOf(
|
||||
arrayOf(
|
||||
arrayOf(Vector2(0.0, 0.0), Vector2(0.0, 1.0), Vector2(1.0, 0.0)),
|
||||
arrayOf(Vector2(0.0, 1.0), Vector2(1.0, 1.0), Vector2(1.0, 0.0)),
|
||||
)
|
||||
geom.setAttribute(
|
||||
"normal",
|
||||
Float32BufferAttribute(
|
||||
Float32Array(arrayOf(
|
||||
0f, 0f, 1f,
|
||||
0f, 0f, 1f,
|
||||
0f, 0f, 1f,
|
||||
0f, 0f, 1f,
|
||||
)),
|
||||
3,
|
||||
),
|
||||
)
|
||||
quad.translate(x.toDouble(), y.toDouble(), -5.0)
|
||||
return quad
|
||||
geom.setAttribute(
|
||||
"uv",
|
||||
Float32BufferAttribute(
|
||||
Float32Array(arrayOf(
|
||||
0f, 1f,
|
||||
0f, 0f,
|
||||
1f, 0f,
|
||||
1f, 1f,
|
||||
)),
|
||||
2,
|
||||
),
|
||||
)
|
||||
geom.setIndex(Uint16BufferAttribute(
|
||||
Uint16Array(arrayOf(
|
||||
0, 2, 1,
|
||||
2, 0, 3,
|
||||
)),
|
||||
1,
|
||||
))
|
||||
|
||||
geom.translate(x.toDouble(), y.toDouble(), -5.0)
|
||||
|
||||
return geom
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
Loading…
Reference in New Issue
Block a user