diff --git a/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/MeshBuilder.kt b/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/MeshBuilder.kt
index 48cf9d8e..60fac306 100644
--- a/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/MeshBuilder.kt
+++ b/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/MeshBuilder.kt
@@ -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)
                 }
diff --git a/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/NinjaGeometryConversion.kt b/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/NinjaGeometryConversion.kt
index 439f284e..b6c1d8ea 100644
--- a/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/NinjaGeometryConversion.kt
+++ b/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/NinjaGeometryConversion.kt
@@ -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 {
diff --git a/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/XvrTextureConversion.kt b/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/XvrTextureConversion.kt
index f21fbe25..0fc6c212 100644
--- a/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/XvrTextureConversion.kt
+++ b/web/src/main/kotlin/world/phantasmal/web/core/rendering/conversion/XvrTextureConversion.kt
@@ -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
diff --git a/web/src/main/kotlin/world/phantasmal/web/externals/three/three.kt b/web/src/main/kotlin/world/phantasmal/web/externals/three/three.kt
index 8c179b4a..7559615c 100644
--- a/web/src/main/kotlin/world/phantasmal/web/externals/three/three.kt
+++ b/web/src/main/kotlin/world/phantasmal/web/externals/three/three.kt
@@ -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,
diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/loading/AreaAssetLoader.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/loading/AreaAssetLoader.kt
index acb4c251..4e60adce 100644
--- a/web/src/main/kotlin/world/phantasmal/web/questEditor/loading/AreaAssetLoader.kt
+++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/loading/AreaAssetLoader.kt
@@ -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) {
diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/loading/EntityAssetLoader.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/loading/EntityAssetLoader.kt
index 0cd24692..5125d2e9 100644
--- a/web/src/main/kotlin/world/phantasmal/web/questEditor/loading/EntityAssetLoader.kt
+++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/loading/EntityAssetLoader.kt
@@ -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.")
         }
diff --git a/web/src/main/kotlin/world/phantasmal/web/viewer/rendering/MeshRenderer.kt b/web/src/main/kotlin/world/phantasmal/web/viewer/rendering/MeshRenderer.kt
index cec43404..638acbcb 100644
--- a/web/src/main/kotlin/world/phantasmal/web/viewer/rendering/MeshRenderer.kt
+++ b/web/src/main/kotlin/world/phantasmal/web/viewer/rendering/MeshRenderer.kt
@@ -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)
diff --git a/web/src/main/kotlin/world/phantasmal/web/viewer/rendering/TextureRenderer.kt b/web/src/main/kotlin/world/phantasmal/web/viewer/rendering/TextureRenderer.kt
index 56e01f8f..8859c31a 100644
--- a/web/src/main/kotlin/world/phantasmal/web/viewer/rendering/TextureRenderer.kt
+++ b/web/src/main/kotlin/world/phantasmal/web/viewer/rendering/TextureRenderer.kt
@@ -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)