Worked on mipmapping and anisotropy.

This commit is contained in:
Daan Vanden Bosch 2023-06-02 22:35:00 +02:00
parent 423c3e252b
commit 62a49b067c
8 changed files with 315 additions and 98 deletions

View File

@ -13,6 +13,7 @@ import world.phantasmal.webui.obj
class MeshBuilder(
private val textures: List<XvrTexture?> = emptyList(),
private val textureCache: UnsafeMap<Int, Texture?> = UnsafeMap(),
private val anisotropy: Int = 1,
) {
private val positions = mutableListOf<Vector3>()
private val normals = mutableListOf<Vector3>()
@ -197,7 +198,7 @@ class MeshBuilder(
if (tex == null) {
tex = textures.getOrNull(group.textureIndex)?.let { xvm ->
xvrTextureToThree(xvm)
xvrTextureToThree(xvm, anisotropy = anisotropy)
}
textureCache.set(group.textureIndex, tex)
}

View File

@ -8,11 +8,40 @@ import world.phantasmal.core.asArray
import world.phantasmal.core.isBitSet
import world.phantasmal.core.jsArrayOf
import world.phantasmal.core.unsafe.UnsafeMap
import world.phantasmal.psolib.fileFormats.*
import world.phantasmal.psolib.fileFormats.ninja.*
import world.phantasmal.psolib.fileFormats.AreaGeometry
import world.phantasmal.psolib.fileFormats.AreaObject
import world.phantasmal.psolib.fileFormats.AreaSection
import world.phantasmal.psolib.fileFormats.CollisionGeometry
import world.phantasmal.psolib.fileFormats.CollisionTriangle
import world.phantasmal.psolib.fileFormats.ninja.NinjaModel
import world.phantasmal.psolib.fileFormats.ninja.NinjaObject
import world.phantasmal.psolib.fileFormats.ninja.NjModel
import world.phantasmal.psolib.fileFormats.ninja.NjObject
import world.phantasmal.psolib.fileFormats.ninja.XjModel
import world.phantasmal.psolib.fileFormats.ninja.XjObject
import world.phantasmal.psolib.fileFormats.ninja.XvrTexture
import world.phantasmal.web.core.dot
import world.phantasmal.web.core.toQuaternion
import world.phantasmal.web.externals.three.*
import world.phantasmal.web.externals.three.Bone
import world.phantasmal.web.externals.three.BufferGeometry
import world.phantasmal.web.externals.three.Color
import world.phantasmal.web.externals.three.DoubleSide
import world.phantasmal.web.externals.three.Euler
import world.phantasmal.web.externals.three.Float32BufferAttribute
import world.phantasmal.web.externals.three.Group
import world.phantasmal.web.externals.three.InstancedMesh
import world.phantasmal.web.externals.three.Material
import world.phantasmal.web.externals.three.Matrix3
import world.phantasmal.web.externals.three.Matrix4
import world.phantasmal.web.externals.three.Mesh
import world.phantasmal.web.externals.three.MeshBasicMaterial
import world.phantasmal.web.externals.three.MeshLambertMaterial
import world.phantasmal.web.externals.three.Quaternion
import world.phantasmal.web.externals.three.SkinnedMesh
import world.phantasmal.web.externals.three.Texture
import world.phantasmal.web.externals.three.Uint16BufferAttribute
import world.phantasmal.web.externals.three.Vector2
import world.phantasmal.web.externals.three.Vector3
import world.phantasmal.webui.obj
import kotlin.collections.component1
import kotlin.collections.component2
@ -86,8 +115,9 @@ fun ninjaObjectToMesh(
textures: List<XvrTexture?>,
defaultMaterial: Material? = null,
boundingVolumes: Boolean = false,
anisotropy: Int = 1,
): Mesh {
val builder = MeshBuilder(textures)
val builder = MeshBuilder(textures, anisotropy = anisotropy)
defaultMaterial?.let { builder.defaultMaterial(defaultMaterial) }
ninjaObjectToMeshBuilder(ninjaObject, builder)
return builder.buildMesh(boundingVolumes)
@ -99,8 +129,9 @@ fun ninjaObjectToInstancedMesh(
maxInstances: Int,
defaultMaterial: Material? = null,
boundingVolumes: Boolean = false,
anisotropy: Int = 1,
): InstancedMesh {
val builder = MeshBuilder(textures)
val builder = MeshBuilder(textures, anisotropy = anisotropy)
defaultMaterial?.let { builder.defaultMaterial(defaultMaterial) }
ninjaObjectToMeshBuilder(ninjaObject, builder)
return builder.buildInstancedMesh(maxInstances, boundingVolumes)
@ -111,8 +142,9 @@ fun ninjaObjectToSkinnedMesh(
textures: List<XvrTexture?>,
defaultMaterial: Material? = null,
boundingVolumes: Boolean = false,
anisotropy: Int = 1,
): SkinnedMesh {
val builder = MeshBuilder(textures)
val builder = MeshBuilder(textures, anisotropy = anisotropy)
defaultMaterial?.let { builder.defaultMaterial(defaultMaterial) }
ninjaObjectToMeshBuilder(ninjaObject, builder)
return builder.buildSkinnedMesh(boundingVolumes)
@ -129,6 +161,7 @@ fun ninjaObjectToMeshBuilder(
fun renderGeometryToGroup(
renderGeometry: AreaGeometry,
textures: List<XvrTexture?>,
anisotropy: Int = 1,
processMesh: (AreaSection, AreaObject, Mesh) -> Unit = { _, _, _ -> },
): Group {
val group = Group()
@ -145,6 +178,7 @@ fun renderGeometryToGroup(
section,
sectionIndex,
areaObj,
anisotropy,
processMesh,
)
)
@ -159,6 +193,7 @@ fun renderGeometryToGroup(
section,
sectionIndex,
areaObj,
anisotropy,
processMesh,
)
)
@ -214,13 +249,14 @@ private fun areaObjectToMesh(
section: AreaSection,
sectionIndex: Int,
areaObj: AreaObject,
anisotropy: Int,
processMesh: (AreaSection, AreaObject, Mesh) -> Unit,
): Mesh {
val cachedMesh = meshCache.get(areaObj.xjObject)
val mesh: Mesh
if (cachedMesh == null) {
val builder = MeshBuilder(textures, textureCache)
val builder = MeshBuilder(textures, textureCache, anisotropy)
ninjaObjectToMeshBuilder(areaObj.xjObject, builder)
builder.defaultMaterial(MeshLambertMaterial(obj {

View File

@ -6,11 +6,32 @@ import org.khronos.webgl.get
import org.khronos.webgl.set
import world.phantasmal.psolib.cursor.cursor
import world.phantasmal.psolib.fileFormats.ninja.XvrTexture
import world.phantasmal.web.externals.three.*
import world.phantasmal.web.externals.three.CompressedPixelFormat
import world.phantasmal.web.externals.three.CompressedTexture
import world.phantasmal.web.externals.three.DataTexture
import world.phantasmal.web.externals.three.LinearFilter
import world.phantasmal.web.externals.three.Mipmap
import world.phantasmal.web.externals.three.MirroredRepeatWrapping
import world.phantasmal.web.externals.three.PixelFormat
import world.phantasmal.web.externals.three.RGBAFormat
import world.phantasmal.web.externals.three.RGBA_S3TC_DXT1_Format
import world.phantasmal.web.externals.three.RGBA_S3TC_DXT3_Format
import world.phantasmal.web.externals.three.RGBFormat
import world.phantasmal.web.externals.three.Texture
import world.phantasmal.web.externals.three.TextureDataType
import world.phantasmal.web.externals.three.TextureFilter
import world.phantasmal.web.externals.three.UnsignedShort5551Type
import world.phantasmal.web.externals.three.UnsignedShort565Type
import world.phantasmal.webui.obj
import kotlin.math.roundToInt
fun xvrTextureToThree(xvr: XvrTexture, filter: TextureFilter = LinearFilter): Texture =
fun xvrTextureToThree(
xvr: XvrTexture,
magFilter: TextureFilter = LinearFilter,
// TODO: Use LinearMipmapLinearFilter once we figure out mipmapping.
minFilter: TextureFilter = LinearFilter,
anisotropy: Int = 1,
): Texture =
when (xvr.format.second) {
// D3DFMT_R5G6B5
2 -> createDataTexture(
@ -19,7 +40,9 @@ fun xvrTextureToThree(xvr: XvrTexture, filter: TextureFilter = LinearFilter): Te
xvr.height,
RGBFormat,
UnsignedShort565Type,
filter,
magFilter,
minFilter,
anisotropy,
)
// D3DFMT_A1R5G5B5
3 -> {
@ -38,26 +61,84 @@ fun xvrTextureToThree(xvr: XvrTexture, filter: TextureFilter = LinearFilter): Te
xvr.height,
RGBAFormat,
UnsignedShort5551Type,
filter,
magFilter,
minFilter,
anisotropy,
)
}
// D3DFMT_DXT1
6 -> createCompressedTexture(
Uint8Array(xvr.data.arrayBuffer, 0, (xvr.width * xvr.height) / 2),
xvr.width,
xvr.height,
RGBA_S3TC_DXT1_Format,
filter,
)
6 -> {
val mipmaps = mutableListOf<Mipmap>()
var byteOffset = 0
var width = xvr.width
var height = xvr.height
while (byteOffset < xvr.data.size && width * height > 0) {
val byteSize = (width * height) / 2
mipmaps.add(obj {
this.data = Uint8Array(xvr.data.arrayBuffer, byteOffset, byteSize)
this.width = width
this.height = height
})
byteOffset += byteSize
width /= 2
height /= 2
// TODO: Figure out what the problem with mipmaps is and remove this break.
// Do we interpret the XVR format incorrectly or is there a problem with
// Three.js/WebGL?
break
}
createCompressedTexture(
mipmaps.toTypedArray(),
xvr.width,
xvr.height,
RGBA_S3TC_DXT1_Format,
magFilter,
minFilter,
anisotropy,
)
}
// D3DFMT_DXT2
// TODO: Correctly interpret this (DXT2 is basically DXT3 with premultiplied alpha).
7 -> createCompressedTexture(
Uint8Array(xvr.data.arrayBuffer, 0, xvr.width * xvr.height),
xvr.width,
xvr.height,
RGBA_S3TC_DXT3_Format,
filter,
)
7 -> {
val mipmaps = mutableListOf<Mipmap>()
var byteOffset = 0
var width = xvr.width
var height = xvr.height
while (byteOffset < xvr.data.size && width * height > 0) {
val byteSize = width * height
mipmaps.add(obj {
this.data = Uint8Array(xvr.data.arrayBuffer, byteOffset, byteSize)
this.width = width
this.height = height
})
byteOffset += byteSize
width /= 2
height /= 2
// TODO: Figure out what the problem with mipmaps is and remove this break.
// Do we interpret the XVR format incorrectly or is there a problem with
// Three.js/WebGL?
break
}
createCompressedTexture(
mipmaps.toTypedArray(),
xvr.width,
xvr.height,
RGBA_S3TC_DXT3_Format,
magFilter,
minFilter,
anisotropy,
)
}
// 1 -> D3DFMT_A8R8G8B8
// 4 -> D3DFMT_A4R4G4B4
// 5 -> D3DFMT_P8
@ -83,7 +164,9 @@ private fun createDataTexture(
height: Int,
format: PixelFormat,
type: TextureDataType,
filter: TextureFilter,
magFilter: TextureFilter,
minFilter: TextureFilter,
anisotropy: Int,
): DataTexture =
DataTexture(
data,
@ -93,30 +176,30 @@ private fun createDataTexture(
type,
wrapS = MirroredRepeatWrapping,
wrapT = MirroredRepeatWrapping,
magFilter = filter,
minFilter = filter,
magFilter = magFilter,
minFilter = minFilter,
anisotropy = anisotropy,
)
private fun createCompressedTexture(
data: Uint8Array,
mipmaps: Array<Mipmap>,
width: Int,
height: Int,
format: CompressedPixelFormat,
filter: TextureFilter,
magFilter: TextureFilter,
minFilter: TextureFilter,
anisotropy: Int,
): CompressedTexture {
val texture = CompressedTexture(
arrayOf(obj {
this.data = data
this.width = width
this.height = height
}),
mipmaps,
width,
height,
format,
wrapS = MirroredRepeatWrapping,
wrapT = MirroredRepeatWrapping,
magFilter = filter,
minFilter = filter,
magFilter = magFilter,
minFilter = minFilter,
anisotropy = anisotropy,
)
texture.needsUpdate = true
return texture
@ -165,12 +248,14 @@ private fun xvrTextureToUint8Array(xvr: XvrTexture): Uint8Array {
b = c0b
a = 1.0
}
1 -> {
r = c1r
g = c1g
b = c1b
a = 1.0
}
2 -> {
if (c0 > c1) {
r = (2 * c0r + c1r) / 3
@ -184,6 +269,7 @@ private fun xvrTextureToUint8Array(xvr: XvrTexture): Uint8Array {
a = 1.0
}
}
3 -> {
if (c0 > c1) {
r = (c0r + 2 * c1r) / 3

View File

@ -7,6 +7,7 @@ package world.phantasmal.web.externals.three
import org.khronos.webgl.Float32Array
import org.khronos.webgl.Int32Array
import org.khronos.webgl.Uint16Array
import org.khronos.webgl.Uint8Array
import org.w3c.dom.HTMLCanvasElement
external interface Vector
@ -235,6 +236,7 @@ open external class WebGLRenderer(
var autoClearColor: Boolean
var debug: WebGLDebug
var capabilities: WebGLCapabilities
override fun render(scene: Object3D, camera: Camera)
@ -253,6 +255,10 @@ external interface WebGLDebug {
var checkShaderErrors: Boolean
}
external interface WebGLCapabilities {
fun getMaxAnisotropy(): Int
}
open external class Object3D {
/**
* Optional name of the object (doesn't need to be unique).
@ -686,7 +692,7 @@ external class DataTexture(
wrapT: Wrapping = definedExternally,
magFilter: TextureFilter = definedExternally,
minFilter: TextureFilter = definedExternally,
anisotropy: Double = definedExternally,
anisotropy: Int = definedExternally,
encoding: TextureEncoding = definedExternally,
) : Texture
@ -707,14 +713,10 @@ external object MirroredRepeatWrapping : Wrapping
external interface TextureFilter
external object NearestFilter : TextureFilter
external object NearestMipmapNearestFilter : TextureFilter
external object NearestMipMapNearestFilter : TextureFilter
external object NearestMipmapLinearFilter : TextureFilter
external object NearestMipMapLinearFilter : TextureFilter
external object LinearFilter : TextureFilter
external object LinearMipmapNearestFilter : TextureFilter
external object LinearMipMapNearestFilter : TextureFilter
external object LinearMipmapLinearFilter : TextureFilter
external object LinearMipMapLinearFilter : TextureFilter
external interface TextureDataType
external object UnsignedByteType : TextureDataType
@ -764,7 +766,7 @@ external object RGBM16Encoding : TextureEncoding
external object RGBDEncoding : TextureEncoding
external class CompressedTexture(
mipmaps: Array<dynamic>, /* Should have data, height and width. */
mipmaps: Array<Mipmap>,
width: Int,
height: Int,
format: CompressedPixelFormat = definedExternally,
@ -774,10 +776,16 @@ external class CompressedTexture(
wrapT: Wrapping = definedExternally,
magFilter: TextureFilter = definedExternally,
minFilter: TextureFilter = definedExternally,
anisotropy: Double = definedExternally,
anisotropy: Int = definedExternally,
encoding: TextureEncoding = definedExternally,
) : Texture
external interface Mipmap {
var data: Uint8Array
var width: Int
var height: Int
}
external enum class MOUSE {
LEFT,
MIDDLE,

View File

@ -174,6 +174,7 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
val fix = MANUAL_FIXES[Pair(episode, areaVariant.area.id)]
val sections = mutableMapOf<Int, SectionModel>()
// TODO: Pass anisotropy parameter.
val group =
renderGeometryToGroup(renderGeometry, textures) { renderSection, areaObj, mesh ->
if (fix != null) {

View File

@ -7,14 +7,22 @@ import world.phantasmal.core.Success
import world.phantasmal.psolib.Endianness
import world.phantasmal.psolib.cursor.Cursor
import world.phantasmal.psolib.cursor.cursor
import world.phantasmal.psolib.fileFormats.ninja.*
import world.phantasmal.psolib.fileFormats.ninja.NinjaObject
import world.phantasmal.psolib.fileFormats.ninja.XvrTexture
import world.phantasmal.psolib.fileFormats.ninja.parseNj
import world.phantasmal.psolib.fileFormats.ninja.parseXj
import world.phantasmal.psolib.fileFormats.ninja.parseXvm
import world.phantasmal.psolib.fileFormats.quest.EntityType
import world.phantasmal.psolib.fileFormats.quest.NpcType
import world.phantasmal.psolib.fileFormats.quest.ObjectType
import world.phantasmal.web.core.loading.AssetLoader
import world.phantasmal.web.core.rendering.conversion.ninjaObjectToInstancedMesh
import world.phantasmal.web.core.rendering.disposeObject3DResources
import world.phantasmal.web.externals.three.*
import world.phantasmal.web.externals.three.Color
import world.phantasmal.web.externals.three.CylinderGeometry
import world.phantasmal.web.externals.three.DoubleSide
import world.phantasmal.web.externals.three.InstancedMesh
import world.phantasmal.web.externals.three.MeshLambertMaterial
import world.phantasmal.webui.DisposableContainer
import world.phantasmal.webui.obj
@ -56,6 +64,7 @@ class EntityAssetLoader(private val assetLoader: AssetLoader) : DisposableContai
val textures = loadTextures(type, model)
// TODO: Pass anisotropy parameter.
return ninjaObjectToInstancedMesh(
ninjaObject,
textures,
@ -226,6 +235,7 @@ private fun entityTypeToGeometryFormat(type: EntityType): GeomFormat =
else -> GeomFormat.Nj
}
}
is ObjectType -> {
when (type) {
ObjectType.EasterEgg,
@ -249,6 +259,7 @@ private fun entityTypeToGeometryFormat(type: EntityType): GeomFormat =
else -> GeomFormat.Xj
}
}
else -> {
error("$type not supported.")
}
@ -272,6 +283,7 @@ private fun entityTypeToPath(
GeomFormat.Nj -> "nj"
GeomFormat.Xj -> "xj"
}
AssetType.Texture -> "xvm"
}
@ -296,26 +308,37 @@ private fun entityTypeToPath(
NpcType.Hildebear2 ->
entityTypeToPath(NpcType.Hildebear, assetType, suffix, model, geomFormat)
NpcType.Hildeblue2 ->
entityTypeToPath(NpcType.Hildeblue, assetType, suffix, model, geomFormat)
NpcType.RagRappy2 ->
entityTypeToPath(NpcType.RagRappy, assetType, suffix, model, geomFormat)
NpcType.Monest2 ->
entityTypeToPath(NpcType.Monest, assetType, suffix, model, geomFormat)
NpcType.Mothmant2 ->
entityTypeToPath(NpcType.Mothmant, assetType, suffix, model, geomFormat)
NpcType.PoisonLily2 ->
entityTypeToPath(NpcType.PoisonLily, assetType, suffix, model, geomFormat)
NpcType.NarLily2 ->
entityTypeToPath(NpcType.NarLily, assetType, suffix, model, geomFormat)
NpcType.GrassAssassin2 ->
entityTypeToPath(NpcType.GrassAssassin, assetType, suffix, model, geomFormat)
NpcType.Dimenian2 ->
entityTypeToPath(NpcType.Dimenian, assetType, suffix, model, geomFormat)
NpcType.LaDimenian2 ->
entityTypeToPath(NpcType.LaDimenian, assetType, suffix, model, geomFormat)
NpcType.SoDimenian2 ->
entityTypeToPath(NpcType.SoDimenian, assetType, suffix, model, geomFormat)
NpcType.DarkBelra2 ->
entityTypeToPath(NpcType.DarkBelra, assetType, suffix, model, geomFormat)
@ -323,26 +346,35 @@ private fun entityTypeToPath(
NpcType.SavageWolf2 ->
entityTypeToPath(NpcType.SavageWolf, assetType, suffix, model, geomFormat)
NpcType.BarbarousWolf2 ->
entityTypeToPath(NpcType.BarbarousWolf, assetType, suffix, model, geomFormat)
NpcType.PanArms2 ->
entityTypeToPath(NpcType.PanArms, assetType, suffix, model, geomFormat)
NpcType.Dubchic2 ->
entityTypeToPath(NpcType.Dubchic, assetType, suffix, model, geomFormat)
NpcType.Gilchic2 ->
entityTypeToPath(NpcType.Gilchic, assetType, suffix, model, geomFormat)
NpcType.Garanz2 ->
entityTypeToPath(NpcType.Garanz, assetType, suffix, model, geomFormat)
NpcType.Dubswitch2 ->
entityTypeToPath(NpcType.Dubswitch, assetType, suffix, model, geomFormat)
NpcType.Delsaber2 ->
entityTypeToPath(NpcType.Delsaber, assetType, suffix, model, geomFormat)
NpcType.ChaosSorcerer2 ->
entityTypeToPath(NpcType.ChaosSorcerer, assetType, suffix, model, geomFormat)
else -> "/npcs/${type.name}${fullSuffix}.$extension"
}
}
is ObjectType -> {
when (type) {
// We don't have a model for these objects.
@ -431,6 +463,7 @@ private fun entityTypeToPath(
}
}
}
else -> {
error("$type not supported.")
}

View File

@ -8,11 +8,27 @@ import world.phantasmal.psolib.fileFormats.ninja.NjMotion
import world.phantasmal.psolib.fileFormats.ninja.NjObject
import world.phantasmal.web.core.boundingSphere
import world.phantasmal.web.core.isSkinnedMesh
import world.phantasmal.web.core.rendering.*
import world.phantasmal.web.core.rendering.DisposableThreeRenderer
import world.phantasmal.web.core.rendering.OrbitalCameraInputManager
import world.phantasmal.web.core.rendering.RenderContext
import world.phantasmal.web.core.rendering.Renderer
import world.phantasmal.web.core.rendering.conversion.*
import world.phantasmal.web.core.rendering.conversion.PSO_FRAME_RATE_DOUBLE
import world.phantasmal.web.core.rendering.conversion.collisionGeometryToGroup
import world.phantasmal.web.core.rendering.conversion.createAnimationClip
import world.phantasmal.web.core.rendering.conversion.ninjaObjectToMesh
import world.phantasmal.web.core.rendering.conversion.ninjaObjectToSkinnedMesh
import world.phantasmal.web.core.rendering.conversion.renderGeometryToGroup
import world.phantasmal.web.core.rendering.disposeObject3DResources
import world.phantasmal.web.core.times
import world.phantasmal.web.externals.three.*
import world.phantasmal.web.externals.three.AnimationAction
import world.phantasmal.web.externals.three.AnimationClip
import world.phantasmal.web.externals.three.AnimationMixer
import world.phantasmal.web.externals.three.Clock
import world.phantasmal.web.externals.three.LineBasicMaterial
import world.phantasmal.web.externals.three.Object3D
import world.phantasmal.web.externals.three.PerspectiveCamera
import world.phantasmal.web.externals.three.SkeletonHelper
import world.phantasmal.web.externals.three.Vector3
import world.phantasmal.web.shared.Throttle
import world.phantasmal.web.viewer.stores.NinjaGeometry
import world.phantasmal.web.viewer.stores.ViewerStore
@ -125,15 +141,26 @@ class MeshRenderer(
val obj = ninjaGeometry.obj
if (obj is NjObject) {
ninjaObjectToSkinnedMesh(obj, textures, boundingVolumes = true)
ninjaObjectToSkinnedMesh(
obj,
textures,
boundingVolumes = true,
anisotropy = threeRenderer.capabilities.getMaxAnisotropy() / 2,
)
} else {
ninjaObjectToMesh(obj, textures, boundingVolumes = true)
ninjaObjectToMesh(
obj,
textures,
boundingVolumes = true,
anisotropy = threeRenderer.capabilities.getMaxAnisotropy() / 2,
)
}
}
is NinjaGeometry.Render -> renderGeometryToGroup(
ninjaGeometry.geometry,
textures
textures,
anisotropy = threeRenderer.capabilities.getMaxAnisotropy() / 2,
)
is NinjaGeometry.Collision -> collisionGeometryToGroup(ninjaGeometry.geometry)

View File

@ -5,10 +5,21 @@ import org.khronos.webgl.Float32Array
import org.khronos.webgl.Uint16Array
import org.w3c.dom.HTMLCanvasElement
import world.phantasmal.psolib.fileFormats.ninja.XvrTexture
import world.phantasmal.web.core.rendering.*
import world.phantasmal.web.core.rendering.DisposableThreeRenderer
import world.phantasmal.web.core.rendering.OrbitalCameraInputManager
import world.phantasmal.web.core.rendering.RenderContext
import world.phantasmal.web.core.rendering.Renderer
import world.phantasmal.web.core.rendering.conversion.xvrTextureToThree
import world.phantasmal.web.externals.three.*
import world.phantasmal.web.core.rendering.disposeObject3DResources
import world.phantasmal.web.externals.three.BufferGeometry
import world.phantasmal.web.externals.three.Color
import world.phantasmal.web.externals.three.Float32BufferAttribute
import world.phantasmal.web.externals.three.Mesh
import world.phantasmal.web.externals.three.MeshBasicMaterial
import world.phantasmal.web.externals.three.NearestFilter
import world.phantasmal.web.externals.three.OrthographicCamera
import world.phantasmal.web.externals.three.Uint16BufferAttribute
import world.phantasmal.web.externals.three.Vector3
import world.phantasmal.web.viewer.stores.ViewerStore
import world.phantasmal.webui.obj
import kotlin.math.ceil
@ -23,27 +34,31 @@ class TextureRenderer(
) : Renderer() {
private var meshes = listOf<Mesh>()
override val context = addDisposable(RenderContext(
createCanvas(),
OrthographicCamera(
left = -400.0,
right = 400.0,
top = 300.0,
bottom = -300.0,
near = 1.0,
far = 10.0,
override val context = addDisposable(
RenderContext(
createCanvas(),
OrthographicCamera(
left = -400.0,
right = 400.0,
top = 300.0,
bottom = -300.0,
near = 1.0,
far = 10.0,
)
)
))
)
override val threeRenderer = addDisposable(createThreeRenderer(context.canvas)).renderer
override val inputManager = addDisposable(OrbitalCameraInputManager(
context.canvas,
context.camera,
Vector3(0.0, 0.0, 5.0),
screenSpacePanning = true,
enableRotate = false,
))
override val inputManager = addDisposable(
OrbitalCameraInputManager(
context.canvas,
context.camera,
Vector3(0.0, 0.0, 5.0),
screenSpacePanning = true,
enableRotate = false,
)
)
init {
observeNow(store.currentTextures) {
@ -81,7 +96,7 @@ class TextureRenderer(
meshes = textures.map { xvr ->
val texture =
try {
xvrTextureToThree(xvr, filter = NearestFilter)
xvrTextureToThree(xvr, magFilter = NearestFilter, minFilter = NearestFilter)
} catch (e: Exception) {
logger.error(e) { "Couldn't convert XVR texture." }
null
@ -120,46 +135,56 @@ class TextureRenderer(
geom.setAttribute(
"position",
Float32BufferAttribute(
Float32Array(arrayOf(
-halfWidth, -halfHeight, 0f,
-halfWidth, halfHeight, 0f,
halfWidth, halfHeight, 0f,
halfWidth, -halfHeight, 0f,
)),
Float32Array(
arrayOf(
-halfWidth, -halfHeight, 0f,
-halfWidth, halfHeight, 0f,
halfWidth, halfHeight, 0f,
halfWidth, -halfHeight, 0f,
)
),
3,
),
)
geom.setAttribute(
"normal",
Float32BufferAttribute(
Float32Array(arrayOf(
0f, 0f, 1f,
0f, 0f, 1f,
0f, 0f, 1f,
0f, 0f, 1f,
)),
Float32Array(
arrayOf(
0f, 0f, 1f,
0f, 0f, 1f,
0f, 0f, 1f,
0f, 0f, 1f,
)
),
3,
),
)
geom.setAttribute(
"uv",
Float32BufferAttribute(
Float32Array(arrayOf(
0f, 1f,
0f, 0f,
1f, 0f,
1f, 1f,
)),
Float32Array(
arrayOf(
0f, 1f,
0f, 0f,
1f, 0f,
1f, 1f,
)
),
2,
),
)
geom.setIndex(Uint16BufferAttribute(
Uint16Array(arrayOf(
0, 2, 1,
2, 0, 3,
)),
1,
))
geom.setIndex(
Uint16BufferAttribute(
Uint16Array(
arrayOf(
0, 2, 1,
2, 0, 3,
)
),
1,
)
)
geom.translate(x.toDouble(), y.toDouble(), -5.0)