mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
It's now possible to show the textured render geometry instead of the collision geometry.
This commit is contained in:
parent
969df2f371
commit
e180ac28c9
@ -9,7 +9,10 @@ import world.phantasmal.lib.fileFormats.ninja.XvrTexture
|
||||
import world.phantasmal.web.externals.three.*
|
||||
import world.phantasmal.webui.obj
|
||||
|
||||
class MeshBuilder {
|
||||
class MeshBuilder(
|
||||
private val textures: List<XvrTexture?> = emptyList(),
|
||||
private val textureCache: MutableMap<Int, Texture?> = mutableMapOf(),
|
||||
) {
|
||||
private val positions = mutableListOf<Vector3>()
|
||||
private val normals = mutableListOf<Vector3>()
|
||||
private val uvs = mutableListOf<Vector2>()
|
||||
@ -25,23 +28,21 @@ class MeshBuilder {
|
||||
|
||||
private var defaultMaterial: Material? = null
|
||||
|
||||
private val textures = mutableListOf<XvrTexture?>()
|
||||
|
||||
fun getGroupIndex(
|
||||
textureId: Int?,
|
||||
textureIndex: Int?,
|
||||
alpha: Boolean,
|
||||
additiveBlending: Boolean,
|
||||
): Int {
|
||||
val idx = groups.indexOfFirst {
|
||||
it.textureId == textureId &&
|
||||
val groupIndex = groups.indexOfFirst {
|
||||
it.textureIndex == textureIndex &&
|
||||
it.alpha == alpha &&
|
||||
it.additiveBlending == additiveBlending
|
||||
}
|
||||
|
||||
return if (idx != -1) {
|
||||
idx
|
||||
return if (groupIndex != -1) {
|
||||
groupIndex
|
||||
} else {
|
||||
groups.add(Group(textureId, alpha, additiveBlending))
|
||||
groups.add(Group(textureIndex, alpha, additiveBlending))
|
||||
groups.lastIndex
|
||||
}
|
||||
}
|
||||
@ -87,12 +88,8 @@ class MeshBuilder {
|
||||
defaultMaterial = material
|
||||
}
|
||||
|
||||
fun textures(textures: List<XvrTexture?>) {
|
||||
this.textures.addAll(textures)
|
||||
}
|
||||
|
||||
fun buildMesh(boundingVolumes: Boolean = false): Mesh =
|
||||
build(skinning = false, boundingVolumes).let { (geom, materials) ->
|
||||
build(skinning = false, boundingVolumes) { geom, materials, _ ->
|
||||
Mesh(geom, materials)
|
||||
}
|
||||
|
||||
@ -100,7 +97,7 @@ class MeshBuilder {
|
||||
* Creates an [InstancedMesh] with 0 instances.
|
||||
*/
|
||||
fun buildInstancedMesh(maxInstances: Int, boundingVolumes: Boolean = false): InstancedMesh =
|
||||
build(skinning = false, boundingVolumes).let { (geom, materials) ->
|
||||
build(skinning = false, boundingVolumes) { geom, materials, _ ->
|
||||
InstancedMesh(geom, materials, maxInstances).apply {
|
||||
// Start with 0 instances.
|
||||
count = 0
|
||||
@ -111,17 +108,18 @@ class MeshBuilder {
|
||||
* Creates a [SkinnedMesh] with bones and a skeleton for animation.
|
||||
*/
|
||||
fun buildSkinnedMesh(boundingVolumes: Boolean = false): SkinnedMesh =
|
||||
build(skinning = true, boundingVolumes).let { (geom, materials, bones) ->
|
||||
build(skinning = true, boundingVolumes) { geom, materials, bones ->
|
||||
SkinnedMesh(geom, materials).apply {
|
||||
add(bones[0])
|
||||
bind(Skeleton(bones))
|
||||
}
|
||||
}
|
||||
|
||||
private fun build(
|
||||
private fun <M : Mesh> build(
|
||||
skinning: Boolean,
|
||||
boundingVolumes: Boolean,
|
||||
): Triple<BufferGeometry, Array<Material>, Array<Bone>> {
|
||||
createMesh: (BufferGeometry, Array<Material>, Array<Bone>) -> M,
|
||||
): M {
|
||||
check(positions.size == normals.size)
|
||||
check(uvs.isEmpty() || positions.size == uvs.size)
|
||||
|
||||
@ -173,7 +171,6 @@ class MeshBuilder {
|
||||
val indices = Uint16Array(indexCount)
|
||||
|
||||
var offset = 0
|
||||
val texCache = mutableMapOf<Int, Texture?>()
|
||||
|
||||
val materials = mutableListOf<Material>()
|
||||
|
||||
@ -186,9 +183,9 @@ class MeshBuilder {
|
||||
indices.set(group.indices.asArray(), offset)
|
||||
geom.addGroup(offset, group.indices.length, materials.size)
|
||||
|
||||
val tex = group.textureId?.let { texId ->
|
||||
texCache.getOrPut(texId) {
|
||||
textures.getOrNull(texId)?.let { xvm ->
|
||||
val tex = group.textureIndex?.let { texIndex ->
|
||||
textureCache.getOrPut(texIndex) {
|
||||
textures.getOrNull(texIndex)?.let { xvm ->
|
||||
xvrTextureToThree(xvm)
|
||||
}
|
||||
}
|
||||
@ -226,11 +223,11 @@ class MeshBuilder {
|
||||
geom.computeBoundingSphere()
|
||||
}
|
||||
|
||||
return Triple(geom, materials.toTypedArray(), bones.toTypedArray())
|
||||
return createMesh(geom, materials.toTypedArray(), bones.toTypedArray())
|
||||
}
|
||||
|
||||
private class Group(
|
||||
val textureId: Int?,
|
||||
val textureIndex: Int?,
|
||||
val alpha: Boolean,
|
||||
val additiveBlending: Boolean,
|
||||
) {
|
||||
|
@ -19,19 +19,6 @@ private val tmpNormal = Vector3()
|
||||
private val tmpVec = Vector3()
|
||||
private val tmpNormalMatrix = Matrix3()
|
||||
|
||||
fun ninjaObjectToMesh(
|
||||
ninjaObject: NinjaObject<*>,
|
||||
textures: List<XvrTexture?>,
|
||||
defaultMaterial: Material? = null,
|
||||
boundingVolumes: Boolean = false,
|
||||
): Mesh {
|
||||
val builder = MeshBuilder()
|
||||
defaultMaterial?.let { builder.defaultMaterial(defaultMaterial) }
|
||||
builder.textures(textures)
|
||||
NinjaToMeshConverter(builder).convert(ninjaObject)
|
||||
return builder.buildMesh(boundingVolumes)
|
||||
}
|
||||
|
||||
fun ninjaObjectToInstancedMesh(
|
||||
ninjaObject: NinjaObject<*>,
|
||||
textures: List<XvrTexture>,
|
||||
@ -39,10 +26,9 @@ fun ninjaObjectToInstancedMesh(
|
||||
defaultMaterial: Material? = null,
|
||||
boundingVolumes: Boolean = false,
|
||||
): InstancedMesh {
|
||||
val builder = MeshBuilder()
|
||||
val builder = MeshBuilder(textures)
|
||||
defaultMaterial?.let { builder.defaultMaterial(defaultMaterial) }
|
||||
builder.textures(textures)
|
||||
NinjaToMeshConverter(builder).convert(ninjaObject)
|
||||
ninjaObjectToMeshBuilder(ninjaObject, builder)
|
||||
return builder.buildInstancedMesh(maxInstances, boundingVolumes)
|
||||
}
|
||||
|
||||
@ -52,10 +38,9 @@ fun ninjaObjectToSkinnedMesh(
|
||||
defaultMaterial: Material? = null,
|
||||
boundingVolumes: Boolean = false,
|
||||
): SkinnedMesh {
|
||||
val builder = MeshBuilder()
|
||||
val builder = MeshBuilder(textures)
|
||||
defaultMaterial?.let { builder.defaultMaterial(defaultMaterial) }
|
||||
builder.textures(textures)
|
||||
NinjaToMeshConverter(builder).convert(ninjaObject)
|
||||
ninjaObjectToMeshBuilder(ninjaObject, builder)
|
||||
return builder.buildSkinnedMesh(boundingVolumes)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package world.phantasmal.web.core.rendering.conversion
|
||||
|
||||
import org.khronos.webgl.Uint16Array
|
||||
import org.khronos.webgl.Uint8Array
|
||||
import org.khronos.webgl.set
|
||||
import world.phantasmal.lib.cursor.cursor
|
||||
@ -8,30 +9,77 @@ import world.phantasmal.web.externals.three.*
|
||||
import world.phantasmal.webui.obj
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun xvrTextureToThree(xvr: XvrTexture, filter: TextureFilter = LinearFilter): Texture {
|
||||
val format: CompressedPixelFormat
|
||||
val dataSize: Int
|
||||
|
||||
fun xvrTextureToThree(xvr: XvrTexture, filter: TextureFilter = LinearFilter): Texture =
|
||||
when (xvr.format.second) {
|
||||
6 -> {
|
||||
format = RGBA_S3TC_DXT1_Format
|
||||
dataSize = (xvr.width * xvr.height) / 2
|
||||
}
|
||||
7 -> {
|
||||
format = RGBA_S3TC_DXT3_Format
|
||||
dataSize = xvr.width * xvr.height
|
||||
}
|
||||
2 -> createDataTexture(
|
||||
Uint16Array(xvr.data.arrayBuffer),
|
||||
xvr.width,
|
||||
xvr.height,
|
||||
RGBFormat,
|
||||
UnsignedShort565Type,
|
||||
filter,
|
||||
)
|
||||
// TODO: Figure out what this format actually is.
|
||||
3 -> createDataTexture(
|
||||
Uint8Array(xvr.data.arrayBuffer),
|
||||
xvr.width,
|
||||
xvr.height,
|
||||
LuminanceAlphaFormat,
|
||||
UnsignedByteType,
|
||||
filter,
|
||||
)
|
||||
6 -> createCompressedTexture(
|
||||
Uint8Array(xvr.data.arrayBuffer, 0, (xvr.width * xvr.height) / 2),
|
||||
xvr.width,
|
||||
xvr.height,
|
||||
RGBA_S3TC_DXT1_Format,
|
||||
filter,
|
||||
)
|
||||
7 -> createCompressedTexture(
|
||||
Uint8Array(xvr.data.arrayBuffer, 0, xvr.width * xvr.height),
|
||||
xvr.width,
|
||||
xvr.height,
|
||||
RGBA_S3TC_DXT3_Format,
|
||||
filter,
|
||||
)
|
||||
else -> error("Format ${xvr.format.first}, ${xvr.format.second} not supported.")
|
||||
}
|
||||
|
||||
private fun createDataTexture(
|
||||
data: Any,
|
||||
width: Int,
|
||||
height: Int,
|
||||
format: PixelFormat,
|
||||
type: TextureDataType,
|
||||
filter: TextureFilter,
|
||||
): DataTexture =
|
||||
DataTexture(
|
||||
data,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
type,
|
||||
wrapS = MirroredRepeatWrapping,
|
||||
wrapT = MirroredRepeatWrapping,
|
||||
magFilter = filter,
|
||||
minFilter = filter,
|
||||
)
|
||||
|
||||
private fun createCompressedTexture(
|
||||
data: Uint8Array,
|
||||
width: Int,
|
||||
height: Int,
|
||||
format: CompressedPixelFormat,
|
||||
filter: TextureFilter,
|
||||
): CompressedTexture {
|
||||
val texture = CompressedTexture(
|
||||
arrayOf(obj {
|
||||
data = Uint8Array(xvr.data.arrayBuffer, 0, dataSize)
|
||||
width = xvr.width
|
||||
height = xvr.height
|
||||
this.data = data
|
||||
this.width = width
|
||||
this.height = height
|
||||
}),
|
||||
xvr.width,
|
||||
xvr.height,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
wrapS = MirroredRepeatWrapping,
|
||||
wrapT = MirroredRepeatWrapping,
|
||||
|
@ -562,6 +562,8 @@ external interface MaterialParameters {
|
||||
}
|
||||
|
||||
open external class Material : EventDispatcher {
|
||||
var transparent: Boolean
|
||||
|
||||
/**
|
||||
* This disposes the material. Textures of a material don't get disposed. These needs to be disposed by [Texture].
|
||||
*/
|
||||
@ -598,6 +600,21 @@ open external class Texture : EventDispatcher {
|
||||
fun dispose()
|
||||
}
|
||||
|
||||
external class DataTexture(
|
||||
data: Any, /* TypedArray */
|
||||
width: Int,
|
||||
height: Int,
|
||||
format: PixelFormat = definedExternally,
|
||||
type: TextureDataType = definedExternally,
|
||||
mapping: Mapping = definedExternally,
|
||||
wrapS: Wrapping = definedExternally,
|
||||
wrapT: Wrapping = definedExternally,
|
||||
magFilter: TextureFilter = definedExternally,
|
||||
minFilter: TextureFilter = definedExternally,
|
||||
anisotropy: Double = definedExternally,
|
||||
encoding: TextureEncoding = definedExternally,
|
||||
) : Texture
|
||||
|
||||
external interface Mapping
|
||||
external object UVMapping : Mapping
|
||||
external object CubeReflectionMapping : Mapping
|
||||
@ -638,6 +655,22 @@ external object UnsignedShort5551Type : TextureDataType
|
||||
external object UnsignedShort565Type : TextureDataType
|
||||
external object UnsignedInt248Type : TextureDataType
|
||||
|
||||
external interface PixelFormat
|
||||
external object AlphaFormat : PixelFormat
|
||||
external object RGBFormat : PixelFormat
|
||||
external object RGBAFormat : PixelFormat
|
||||
external object LuminanceFormat : PixelFormat
|
||||
external object LuminanceAlphaFormat : PixelFormat
|
||||
external object RGBEFormat : PixelFormat
|
||||
external object DepthFormat : PixelFormat
|
||||
external object DepthStencilFormat : PixelFormat
|
||||
external object RedFormat : PixelFormat
|
||||
external object RedIntegerFormat : PixelFormat
|
||||
external object RGFormat : PixelFormat
|
||||
external object RGIntegerFormat : PixelFormat
|
||||
external object RGBIntegerFormat : PixelFormat
|
||||
external object RGBAIntegerFormat : PixelFormat
|
||||
|
||||
// DDS / ST3C Compressed texture formats
|
||||
external interface CompressedPixelFormat
|
||||
external object RGB_S3TC_DXT1_Format : CompressedPixelFormat
|
||||
|
@ -96,6 +96,10 @@ class QuestEditorToolbarController(
|
||||
|
||||
val areaSelectEnabled: Val<Boolean> = questEditorStore.currentQuest.isNotNull()
|
||||
|
||||
// Settings
|
||||
|
||||
val showCollisionGeometry: Val<Boolean> = questEditorStore.showCollisionGeometry
|
||||
|
||||
init {
|
||||
addDisposables(
|
||||
uiStore.onGlobalKeyDown(PwToolType.QuestEditor, "Ctrl-O") {
|
||||
@ -246,6 +250,10 @@ class QuestEditorToolbarController(
|
||||
questEditorStore.setCurrentArea(areaAndLabel.area)
|
||||
}
|
||||
|
||||
fun setShowCollisionGeometry(show: Boolean) {
|
||||
questEditorStore.setShowCollisionGeometry(show)
|
||||
}
|
||||
|
||||
private suspend fun setCurrentQuest(quest: Quest) {
|
||||
questEditorStore.setCurrentQuest(convertQuestToModel(quest, areaStore::getVariant))
|
||||
}
|
||||
|
@ -12,6 +12,10 @@ import world.phantasmal.lib.Episode
|
||||
import world.phantasmal.lib.cursor.cursor
|
||||
import world.phantasmal.lib.fileFormats.CollisionObject
|
||||
import world.phantasmal.lib.fileFormats.RenderObject
|
||||
import world.phantasmal.lib.fileFormats.ninja.NinjaObject
|
||||
import world.phantasmal.lib.fileFormats.ninja.XjModel
|
||||
import world.phantasmal.lib.fileFormats.ninja.XvrTexture
|
||||
import world.phantasmal.lib.fileFormats.ninja.parseXvm
|
||||
import world.phantasmal.lib.fileFormats.parseAreaCollisionGeometry
|
||||
import world.phantasmal.lib.fileFormats.parseAreaGeometry
|
||||
import world.phantasmal.web.core.loading.AssetLoader
|
||||
@ -23,6 +27,10 @@ import world.phantasmal.web.questEditor.models.SectionModel
|
||||
import world.phantasmal.webui.DisposableContainer
|
||||
import world.phantasmal.webui.obj
|
||||
|
||||
interface AreaUserData {
|
||||
var section: SectionModel?
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and caches area assets.
|
||||
*/
|
||||
@ -34,9 +42,13 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
private val renderObjectCache = addDisposable(
|
||||
LoadingCache<EpisodeAndAreaVariant, Pair<Object3D, List<SectionModel>>>(
|
||||
{ (episode, areaVariant) ->
|
||||
val buffer = getAreaAsset(episode, areaVariant, AssetType.Render)
|
||||
val obj = parseAreaGeometry(buffer.cursor(Endianness.Little))
|
||||
areaGeometryToObject3DAndSections(obj, areaVariant)
|
||||
val obj = parseAreaGeometry(
|
||||
getAreaAsset(episode, areaVariant, AssetType.Render).cursor(Endianness.Little),
|
||||
)
|
||||
val xvm = parseXvm(
|
||||
getAreaAsset(episode, areaVariant, AssetType.Texture).cursor(Endianness.Little),
|
||||
).unwrap()
|
||||
areaGeometryToObject3DAndSections(obj, xvm.textures, episode, areaVariant)
|
||||
},
|
||||
{ (obj3d) -> disposeObject3DResources(obj3d) },
|
||||
)
|
||||
@ -76,12 +88,7 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
areaVariant: AreaVariantModel,
|
||||
type: AssetType,
|
||||
): ArrayBuffer {
|
||||
val baseUrl = areaVersionToBaseUrl(episode, areaVariant)
|
||||
val suffix = when (type) {
|
||||
AssetType.Render -> "n.rel"
|
||||
AssetType.Collision -> "c.rel"
|
||||
}
|
||||
return assetLoader.loadArrayBuffer(baseUrl + suffix)
|
||||
return assetLoader.loadArrayBuffer(areaAssetUrl(episode, areaVariant, type))
|
||||
}
|
||||
|
||||
private fun addSectionsToCollisionGeometry(collisionGeom: Object3D, renderGeom: Object3D) {
|
||||
@ -119,279 +126,342 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
}
|
||||
}
|
||||
|
||||
private fun areaAssetUrl(
|
||||
episode: Episode,
|
||||
areaVariant: AreaVariantModel,
|
||||
type: AssetType,
|
||||
): String {
|
||||
var areaId = areaVariant.area.id
|
||||
var areaVariantId = areaVariant.id
|
||||
|
||||
// Exception for Seaside Area at Night, variant 1.
|
||||
// Phantasmal World 4 and Lost heart breaker use this to have two tower maps.
|
||||
if (episode == Episode.II && areaId == 16 && areaVariantId == 1) {
|
||||
areaId = 17
|
||||
areaVariantId = 1
|
||||
}
|
||||
|
||||
// Exception for Crater Route 1-4, naming is slightly different.
|
||||
if (episode == Episode.IV && areaId in 1..4) {
|
||||
areaVariantId = areaId - 1
|
||||
}
|
||||
|
||||
val episodeBaseNames = AREA_BASE_NAMES.getValue(episode)
|
||||
|
||||
require(areaId in episodeBaseNames.indices) {
|
||||
"Unknown episode $episode area $areaId."
|
||||
}
|
||||
|
||||
val (baseName, addVariant) = episodeBaseNames[areaId]
|
||||
|
||||
val variant = if (addVariant && type != AssetType.Texture) {
|
||||
"_" + areaVariantId.toString().padStart(2, '0')
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val suffix = when (type) {
|
||||
AssetType.Render -> "n.rel"
|
||||
AssetType.Collision -> "c.rel"
|
||||
AssetType.Texture -> ".xvm"
|
||||
}
|
||||
|
||||
return "/areas/map_${baseName}${variant}${suffix}"
|
||||
}
|
||||
|
||||
private fun areaGeometryToObject3DAndSections(
|
||||
renderObject: RenderObject,
|
||||
textures: List<XvrTexture>,
|
||||
episode: Episode,
|
||||
areaVariant: AreaVariantModel,
|
||||
): Pair<Object3D, List<SectionModel>> {
|
||||
val sections = mutableListOf<SectionModel>()
|
||||
val group = Group()
|
||||
val textureCache = mutableMapOf<Int, Texture?>()
|
||||
|
||||
for ((i, section) in renderObject.sections.withIndex()) {
|
||||
val sectionModel = if (section.id >= 0) {
|
||||
SectionModel(
|
||||
section.id,
|
||||
vec3ToThree(section.position),
|
||||
vec3ToEuler(section.rotation),
|
||||
areaVariant,
|
||||
).also(sections::add)
|
||||
} else null
|
||||
|
||||
for (obj in section.objects) {
|
||||
val builder = MeshBuilder(textures, textureCache)
|
||||
ninjaObjectToMeshBuilder(obj, builder)
|
||||
|
||||
builder.defaultMaterial(MeshBasicMaterial(obj {
|
||||
color = Color().setHSL((i % 7) / 7.0, 1.0, .5)
|
||||
transparent = true
|
||||
opacity = .25
|
||||
side = DoubleSide
|
||||
}))
|
||||
|
||||
val mesh = builder.buildMesh()
|
||||
|
||||
if (shouldRenderOnTop(obj, episode, areaVariant)) {
|
||||
mesh.renderOrder = 1
|
||||
}
|
||||
|
||||
mesh.position.setFromVec3(section.position)
|
||||
mesh.rotation.setFromVec3(section.rotation)
|
||||
mesh.updateMatrixWorld()
|
||||
|
||||
sectionModel?.let {
|
||||
(mesh.userData.unsafeCast<AreaUserData>()).section = sectionModel
|
||||
}
|
||||
|
||||
group.add(mesh)
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(group, sections)
|
||||
}
|
||||
|
||||
private fun shouldRenderOnTop(
|
||||
obj: NinjaObject<XjModel>,
|
||||
episode: Episode,
|
||||
areaVariant: AreaVariantModel,
|
||||
): Boolean {
|
||||
// Manual fixes for various areas. Might not be necessary anymore once order-independent
|
||||
// rendering is implemented.
|
||||
val textureIds: Set<Int> = when {
|
||||
// Pioneer 2
|
||||
episode == Episode.I && areaVariant.area.id == 0 ->
|
||||
setOf(70, 71, 72, 126, 127, 155, 156, 198, 230, 231, 232, 233, 234)
|
||||
// Forest 1
|
||||
episode == Episode.I && areaVariant.area.id == 1 ->
|
||||
setOf(12, 41)
|
||||
// Mine 2
|
||||
episode == Episode.I && areaVariant.area.id == 7 ->
|
||||
setOf(0, 1, 7, 8, 17, 23, 56, 57, 58, 59, 60, 83)
|
||||
// Ruins 1
|
||||
episode == Episode.I && areaVariant.area.id == 8 ->
|
||||
setOf(1, 21, 22, 27, 28, 43, 51, 59, 70, 72, 75)
|
||||
// Lab
|
||||
episode == Episode.II && areaVariant.area.id == 0 ->
|
||||
setOf(36, 37, 38, 48, 60, 67, 79, 80)
|
||||
// Central Control Area
|
||||
episode == Episode.II && areaVariant.area.id == 5 ->
|
||||
(0..59).toSet() + setOf(69, 77)
|
||||
else ->
|
||||
return false
|
||||
}
|
||||
|
||||
fun recurse(obj: NinjaObject<XjModel>): Boolean {
|
||||
obj.model?.meshes?.let { meshes ->
|
||||
for (mesh in meshes) {
|
||||
mesh.material.textureId?.let {
|
||||
if (it in textureIds) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj.children.any(::recurse)
|
||||
}
|
||||
|
||||
return recurse(obj)
|
||||
}
|
||||
|
||||
private fun areaCollisionGeometryToObject3D(
|
||||
obj: CollisionObject,
|
||||
episode: Episode,
|
||||
areaVariant: AreaVariantModel,
|
||||
): Object3D {
|
||||
val group = Group()
|
||||
group.name = "Collision Geometry $episode-${areaVariant.area.id}-${areaVariant.id}"
|
||||
|
||||
for (collisionMesh in obj.meshes) {
|
||||
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
|
||||
val isVegetation = (triangle.flags and 0b10000) != 0
|
||||
val isGround = (triangle.flags and 0b1) != 0
|
||||
val materialIndex = when {
|
||||
isSectionTransition -> 3
|
||||
isVegetation -> 2
|
||||
isGround -> 1
|
||||
else -> 0
|
||||
}
|
||||
|
||||
// Filter out walls.
|
||||
if (materialIndex != 0) {
|
||||
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 (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()
|
||||
|
||||
group.add(
|
||||
Mesh(geom, COLLISION_MATERIALS).apply {
|
||||
renderOrder = 1
|
||||
}
|
||||
)
|
||||
|
||||
group.add(
|
||||
Mesh(geom, COLLISION_WIREFRAME_MATERIALS).apply {
|
||||
renderOrder = 2
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
private data class EpisodeAndAreaVariant(
|
||||
val episode: Episode,
|
||||
val areaVariant: AreaVariantModel,
|
||||
)
|
||||
|
||||
private enum class AssetType {
|
||||
Render, Collision
|
||||
Render, Collision, Texture
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DOWN = Vector3(.0, -1.0, .0)
|
||||
private val UP = Vector3(.0, 1.0, .0)
|
||||
|
||||
private val COLLISION_MATERIALS: Array<Material> = arrayOf(
|
||||
// Wall
|
||||
MeshBasicMaterial(obj {
|
||||
color = Color(0x80c0d0)
|
||||
transparent = true
|
||||
opacity = .25
|
||||
}),
|
||||
// Ground
|
||||
MeshLambertMaterial(obj {
|
||||
color = Color(0x405050)
|
||||
side = DoubleSide
|
||||
}),
|
||||
// Vegetation
|
||||
MeshLambertMaterial(obj {
|
||||
color = Color(0x306040)
|
||||
side = DoubleSide
|
||||
}),
|
||||
// Section transition zone
|
||||
MeshLambertMaterial(obj {
|
||||
color = Color(0x402050)
|
||||
side = DoubleSide
|
||||
}),
|
||||
)
|
||||
|
||||
private val COLLISION_WIREFRAME_MATERIALS: Array<Material> = arrayOf(
|
||||
// Wall
|
||||
MeshBasicMaterial(obj {
|
||||
color = Color(0x90d0e0)
|
||||
wireframe = true
|
||||
transparent = true
|
||||
opacity = .3
|
||||
}),
|
||||
// Ground
|
||||
MeshBasicMaterial(obj {
|
||||
color = Color(0x506060)
|
||||
wireframe = true
|
||||
}),
|
||||
// Vegetation
|
||||
MeshBasicMaterial(obj {
|
||||
color = Color(0x405050)
|
||||
wireframe = true
|
||||
}),
|
||||
// Section transition zone
|
||||
MeshBasicMaterial(obj {
|
||||
color = Color(0x503060)
|
||||
wireframe = true
|
||||
}),
|
||||
)
|
||||
|
||||
private val AREA_BASE_NAMES: Map<Episode, List<Pair<String, Boolean>>> = mapOf(
|
||||
Episode.I to listOf(
|
||||
Pair("city00", true),
|
||||
Pair("forest01", false),
|
||||
Pair("forest02", false),
|
||||
Pair("cave01", true),
|
||||
Pair("cave02", true),
|
||||
Pair("cave03", true),
|
||||
Pair("machine01", true),
|
||||
Pair("machine02", true),
|
||||
Pair("ancient01", true),
|
||||
Pair("ancient02", true),
|
||||
Pair("ancient03", true),
|
||||
Pair("boss01", false),
|
||||
Pair("boss02", false),
|
||||
Pair("boss03", false),
|
||||
Pair("darkfalz00", false),
|
||||
),
|
||||
Episode.II to listOf(
|
||||
Pair("labo00", true),
|
||||
Pair("ruins01", true),
|
||||
Pair("ruins02", true),
|
||||
Pair("space01", true),
|
||||
Pair("space02", true),
|
||||
Pair("jungle01", true),
|
||||
Pair("jungle02", true),
|
||||
Pair("jungle03", true),
|
||||
Pair("jungle04", true),
|
||||
Pair("jungle05", true),
|
||||
Pair("seabed01", true),
|
||||
Pair("seabed02", true),
|
||||
Pair("boss05", false),
|
||||
Pair("boss06", false),
|
||||
Pair("boss07", false),
|
||||
Pair("boss08", false),
|
||||
Pair("jungle06", true),
|
||||
Pair("jungle07", true),
|
||||
),
|
||||
Episode.IV to listOf(
|
||||
Pair("city02", true),
|
||||
Pair("wilds01", true),
|
||||
Pair("wilds01", true),
|
||||
Pair("wilds01", true),
|
||||
Pair("wilds01", true),
|
||||
Pair("crater01", true),
|
||||
Pair("desert01", true),
|
||||
Pair("desert02", true),
|
||||
Pair("desert03", true),
|
||||
Pair("boss09", true),
|
||||
)
|
||||
)
|
||||
|
||||
private val raycaster = Raycaster()
|
||||
private val tmpVec = Vector3()
|
||||
private val tmpIntersections = arrayOf<Intersection>()
|
||||
}
|
||||
}
|
||||
|
||||
interface AreaUserData {
|
||||
var section: SectionModel?
|
||||
}
|
||||
|
||||
private val COLLISION_MATERIALS: Array<Material> = arrayOf(
|
||||
// Wall
|
||||
MeshBasicMaterial(obj {
|
||||
color = Color(0x80c0d0)
|
||||
transparent = true
|
||||
opacity = .25
|
||||
}),
|
||||
// Ground
|
||||
MeshLambertMaterial(obj {
|
||||
color = Color(0x405050)
|
||||
side = DoubleSide
|
||||
}),
|
||||
// Vegetation
|
||||
MeshLambertMaterial(obj {
|
||||
color = Color(0x306040)
|
||||
side = DoubleSide
|
||||
}),
|
||||
// Section transition zone
|
||||
MeshLambertMaterial(obj {
|
||||
color = Color(0x402050)
|
||||
side = DoubleSide
|
||||
}),
|
||||
)
|
||||
|
||||
private val COLLISION_WIREFRAME_MATERIALS: Array<Material> = arrayOf(
|
||||
// Wall
|
||||
MeshBasicMaterial(obj {
|
||||
color = Color(0x90d0e0)
|
||||
wireframe = true
|
||||
transparent = true
|
||||
opacity = .3
|
||||
}),
|
||||
// Ground
|
||||
MeshBasicMaterial(obj {
|
||||
color = Color(0x506060)
|
||||
wireframe = true
|
||||
}),
|
||||
// Vegetation
|
||||
MeshBasicMaterial(obj {
|
||||
color = Color(0x405050)
|
||||
wireframe = true
|
||||
}),
|
||||
// Section transition zone
|
||||
MeshBasicMaterial(obj {
|
||||
color = Color(0x503060)
|
||||
wireframe = true
|
||||
}),
|
||||
)
|
||||
|
||||
private val AREA_BASE_NAMES: Map<Episode, List<Pair<String, Int>>> = mapOf(
|
||||
Episode.I to listOf(
|
||||
Pair("city00_00", 1),
|
||||
Pair("forest01", 1),
|
||||
Pair("forest02", 1),
|
||||
Pair("cave01_", 6),
|
||||
Pair("cave02_", 5),
|
||||
Pair("cave03_", 6),
|
||||
Pair("machine01_", 6),
|
||||
Pair("machine02_", 6),
|
||||
Pair("ancient01_", 5),
|
||||
Pair("ancient02_", 5),
|
||||
Pair("ancient03_", 5),
|
||||
Pair("boss01", 1),
|
||||
Pair("boss02", 1),
|
||||
Pair("boss03", 1),
|
||||
Pair("darkfalz00", 1),
|
||||
),
|
||||
Episode.II to listOf(
|
||||
Pair("labo00_00", 1),
|
||||
Pair("ruins01_", 3),
|
||||
Pair("ruins02_", 3),
|
||||
Pair("space01_", 3),
|
||||
Pair("space02_", 3),
|
||||
Pair("jungle01_00", 1),
|
||||
Pair("jungle02_00", 1),
|
||||
Pair("jungle03_00", 1),
|
||||
Pair("jungle04_", 3),
|
||||
Pair("jungle05_00", 1),
|
||||
Pair("seabed01_", 3),
|
||||
Pair("seabed02_", 3),
|
||||
Pair("boss05", 1),
|
||||
Pair("boss06", 1),
|
||||
Pair("boss07", 1),
|
||||
Pair("boss08", 1),
|
||||
Pair("jungle06_00", 1),
|
||||
Pair("jungle07_", 5),
|
||||
),
|
||||
Episode.IV to listOf(
|
||||
Pair("city02_00", 1),
|
||||
Pair("wilds01_00", 1),
|
||||
Pair("wilds01_01", 1),
|
||||
Pair("wilds01_02", 1),
|
||||
Pair("wilds01_03", 1),
|
||||
Pair("crater01_00", 1),
|
||||
Pair("desert01_", 3),
|
||||
Pair("desert02_", 3),
|
||||
Pair("desert03_", 3),
|
||||
Pair("boss09_00", 1),
|
||||
)
|
||||
)
|
||||
|
||||
private fun areaVersionToBaseUrl(episode: Episode, areaVariant: AreaVariantModel): String {
|
||||
var areaId = areaVariant.area.id
|
||||
var areaVariantId = areaVariant.id
|
||||
|
||||
// Exception for Seaside Area at Night, variant 1.
|
||||
// Phantasmal World 4 and Lost heart breaker use this to have two tower maps.
|
||||
if (areaId == 16 && areaVariantId == 1) {
|
||||
areaId = 17
|
||||
areaVariantId = 1
|
||||
}
|
||||
|
||||
val episodeBaseNames = AREA_BASE_NAMES.getValue(episode)
|
||||
|
||||
require(areaId in episodeBaseNames.indices) {
|
||||
"Unknown episode $episode area $areaId."
|
||||
}
|
||||
|
||||
val (base_name, variants) = episodeBaseNames[areaId]
|
||||
|
||||
require(areaVariantId in 0 until variants) {
|
||||
"Unknown variant $areaVariantId of area $areaId in episode $episode."
|
||||
}
|
||||
|
||||
val variant = if (variants == 1) {
|
||||
""
|
||||
} else {
|
||||
areaVariantId.toString().padStart(2, '0')
|
||||
}
|
||||
|
||||
return "/maps/map_${base_name}${variant}"
|
||||
}
|
||||
|
||||
private fun areaGeometryToObject3DAndSections(
|
||||
renderObject: RenderObject,
|
||||
areaVariant: AreaVariantModel,
|
||||
): Pair<Object3D, List<SectionModel>> {
|
||||
val sections = mutableListOf<SectionModel>()
|
||||
val obj3d = Group()
|
||||
|
||||
for ((i, section) in renderObject.sections.withIndex()) {
|
||||
val builder = MeshBuilder()
|
||||
|
||||
for (obj in section.objects) {
|
||||
ninjaObjectToMeshBuilder(obj, builder)
|
||||
}
|
||||
|
||||
builder.defaultMaterial(MeshBasicMaterial(obj {
|
||||
color = Color().setHSL((i % 7) / 7.0, 1.0, .5)
|
||||
transparent = true
|
||||
opacity = .25
|
||||
side = DoubleSide
|
||||
}))
|
||||
|
||||
val mesh = builder.buildMesh()
|
||||
|
||||
mesh.position.setFromVec3(section.position)
|
||||
mesh.rotation.setFromVec3(section.rotation)
|
||||
mesh.updateMatrixWorld()
|
||||
|
||||
if (section.id >= 0) {
|
||||
val sectionModel = SectionModel(
|
||||
section.id,
|
||||
vec3ToThree(section.position),
|
||||
vec3ToEuler(section.rotation),
|
||||
areaVariant,
|
||||
)
|
||||
sections.add(sectionModel)
|
||||
(mesh.userData.unsafeCast<AreaUserData>()).section = sectionModel
|
||||
}
|
||||
|
||||
obj3d.add(mesh)
|
||||
}
|
||||
|
||||
return Pair(obj3d, sections)
|
||||
}
|
||||
|
||||
private fun areaCollisionGeometryToObject3D(
|
||||
obj: CollisionObject,
|
||||
episode: Episode,
|
||||
areaVariant: AreaVariantModel,
|
||||
): Object3D {
|
||||
val group = Group()
|
||||
group.name = "Collision Geometry $episode-${areaVariant.area.id}-${areaVariant.id}"
|
||||
|
||||
for (collisionMesh in obj.meshes) {
|
||||
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
|
||||
val isVegetation = (triangle.flags and 0b10000) != 0
|
||||
val isGround = (triangle.flags and 0b1) != 0
|
||||
val materialIndex = when {
|
||||
isSectionTransition -> 3
|
||||
isVegetation -> 2
|
||||
isGround -> 1
|
||||
else -> 0
|
||||
}
|
||||
|
||||
// Filter out walls.
|
||||
if (materialIndex != 0) {
|
||||
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 (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()
|
||||
|
||||
group.add(
|
||||
Mesh(geom, COLLISION_MATERIALS).apply {
|
||||
renderOrder = 1
|
||||
}
|
||||
)
|
||||
|
||||
group.add(
|
||||
Mesh(geom, COLLISION_WIREFRAME_MATERIALS).apply {
|
||||
renderOrder = 2
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ class AreaMeshManager(
|
||||
) {
|
||||
suspend fun load(episode: Episode?, areaVariant: AreaVariantModel?) {
|
||||
renderContext.clearCollisionGeometry()
|
||||
renderContext.clearRenderGeometry()
|
||||
|
||||
if (episode == null || areaVariant == null) {
|
||||
return
|
||||
@ -22,6 +23,8 @@ class AreaMeshManager(
|
||||
try {
|
||||
renderContext.collisionGeometry =
|
||||
areaAssetLoader.loadCollisionGeometry(episode, areaVariant)
|
||||
renderContext.renderGeometry =
|
||||
areaAssetLoader.loadRenderGeometry(episode, areaVariant)
|
||||
} catch (e: CancellationException) {
|
||||
// Do nothing.
|
||||
} catch (e: Exception) {
|
||||
|
@ -55,5 +55,10 @@ class QuestEditorMeshManager(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
observe(questEditorStore.showCollisionGeometry) {
|
||||
renderContext.collisionGeometryVisible = it
|
||||
renderContext.renderGeometryVisible = !it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,30 @@ class QuestRenderContext(
|
||||
scene.add(this)
|
||||
}
|
||||
|
||||
var collisionGeometryVisible = true
|
||||
set(visible) {
|
||||
field = visible
|
||||
collisionGeometry.visible = visible
|
||||
}
|
||||
|
||||
var renderGeometryVisible = false
|
||||
set(visible) {
|
||||
field = visible
|
||||
renderGeometry.visible = visible
|
||||
}
|
||||
|
||||
var collisionGeometry: Object3D = DEFAULT_COLLISION_GEOMETRY
|
||||
set(geom) {
|
||||
scene.remove(field)
|
||||
geom.visible = collisionGeometryVisible
|
||||
field = geom
|
||||
scene.add(geom)
|
||||
}
|
||||
|
||||
var renderGeometry: Object3D = DEFAULT_RENDER_GEOMETRY
|
||||
set(geom) {
|
||||
scene.remove(field)
|
||||
geom.visible = renderGeometryVisible
|
||||
field = geom
|
||||
scene.add(geom)
|
||||
}
|
||||
@ -26,9 +47,16 @@ class QuestRenderContext(
|
||||
collisionGeometry = DEFAULT_COLLISION_GEOMETRY
|
||||
}
|
||||
|
||||
fun clearRenderGeometry() {
|
||||
renderGeometry = DEFAULT_RENDER_GEOMETRY
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEFAULT_COLLISION_GEOMETRY = Group().apply {
|
||||
name = "Default Collision Geometry"
|
||||
}
|
||||
private val DEFAULT_RENDER_GEOMETRY = Group().apply {
|
||||
name = "Default Render Geometry"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package world.phantasmal.web.questEditor.rendering
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.w3c.dom.HTMLCanvasElement
|
||||
import world.phantasmal.web.core.rendering.DisposableThreeRenderer
|
||||
import world.phantasmal.web.core.rendering.Renderer
|
||||
@ -22,8 +21,8 @@ class QuestRenderer(
|
||||
fov = 45.0,
|
||||
aspect = 1.0,
|
||||
near = 10.0,
|
||||
far = 5_000.0
|
||||
)
|
||||
far = 5_000.0,
|
||||
),
|
||||
))
|
||||
|
||||
override val threeRenderer = addDisposable(createThreeRenderer(context.canvas)).renderer
|
||||
|
@ -28,6 +28,7 @@ class QuestEditorStore(
|
||||
private val _highlightedEntity = mutableVal<QuestEntityModel<*, *>?>(null)
|
||||
private val _selectedEntity = mutableVal<QuestEntityModel<*, *>?>(null)
|
||||
private val mainUndo = UndoStack(undoManager)
|
||||
private val _showCollisionGeometry = mutableVal(true)
|
||||
|
||||
val runner = QuestRunner()
|
||||
val currentQuest: Val<QuestModel?> = _currentQuest
|
||||
@ -45,11 +46,14 @@ class QuestEditorStore(
|
||||
val selectedEntity: Val<QuestEntityModel<*, *>?> = _selectedEntity
|
||||
|
||||
val questEditingEnabled: Val<Boolean> = currentQuest.isNotNull() and !runner.running
|
||||
|
||||
val canUndo: Val<Boolean> = questEditingEnabled and undoManager.canUndo
|
||||
val firstUndo: Val<Action?> = undoManager.firstUndo
|
||||
val canRedo: Val<Boolean> = questEditingEnabled and undoManager.canRedo
|
||||
val firstRedo: Val<Action?> = undoManager.firstRedo
|
||||
|
||||
val showCollisionGeometry: Val<Boolean> = _showCollisionGeometry
|
||||
|
||||
init {
|
||||
observe(uiStore.currentTool) { tool ->
|
||||
if (tool == PwToolType.QuestEditor) {
|
||||
@ -202,4 +206,8 @@ class QuestEditorStore(
|
||||
}
|
||||
mainUndo.push(action)
|
||||
}
|
||||
|
||||
fun setShowCollisionGeometry(show: Boolean) {
|
||||
_showCollisionGeometry.value = show
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,14 @@ class QuestEditorToolbarWidget(private val ctrl: QuestEditorToolbarController) :
|
||||
selected = ctrl.currentArea,
|
||||
onSelect = ctrl::setCurrentArea,
|
||||
),
|
||||
Checkbox(
|
||||
label = "Simple view",
|
||||
tooltip = value(
|
||||
"Whether the collision or the render geometry should be shown",
|
||||
),
|
||||
checked = ctrl.showCollisionGeometry,
|
||||
onChange = ctrl::setShowCollisionGeometry,
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
|
BIN
web/src/main/resources/assets/areas/map_ancient01.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_ancient01.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_ancient02.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_ancient02.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_ancient03.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_ancient03.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_boss01.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_boss01.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_boss02.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_boss02.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_boss03.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_boss03.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_boss05.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_boss05.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_boss05m.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_boss05m.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_boss06.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_boss06.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_boss07.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_boss07.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_boss08.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_boss08.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_boss08m.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_boss08m.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_boss09.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_boss09.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_cave01.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_cave01.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_cave02.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_cave02.xvm
Normal file
Binary file not shown.
BIN
web/src/main/resources/assets/areas/map_cave03.xvm
Normal file
BIN
web/src/main/resources/assets/areas/map_cave03.xvm
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user