Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ Advantages:
Disadvantages:
- code needs to be ported from cpp to java
- code needs to be maintained
- a little slower compared to cpp when loading big meshes if not using assbin
- a little slower compared to cpp when loading big meshes if not using assbin
10 changes: 4 additions & 6 deletions src/main/kotlin/assimp/BaseImporter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ abstract class BaseImporter {
/** Currently set progress handler. */
var progress: ProgressHandler? = null

var ioSystem: IOSystem = ASSIMP.defaultIOSystem
/** Returns whether the class can handle the format of the given file.
*.
* The implementation should be as quick as possible. A check for the file extension is enough. If no suitable
Expand Down Expand Up @@ -47,7 +46,8 @@ abstract class BaseImporter {
* exception is thrown somewhere in internReadFile(), this function will catch it and transform it into a suitable
* response to the caller.
*/
fun readFile(imp: Importer, pIOHandler: IOSystem = ioSystem, filePath: String): AiScene? {
fun readFile(imp: Importer, ioHandler: IOSystem = ASSIMP.defaultIOSystem, filePath: String): AiScene? {

progress = imp.progressHandler
assert(progress != null)

Expand All @@ -59,7 +59,7 @@ abstract class BaseImporter {

// dispatch importing
try {
internReadFile(filePath, pIOHandler, sc)
internReadFile(filePath, ioHandler, sc)
} catch (err: Exception) {
// extract error description
err.printStackTrace()
Expand Down Expand Up @@ -119,9 +119,7 @@ abstract class BaseImporter {
* @param file Path of the file to be imported.
* @param scene The scene object to hold the imported data. Null is not a valid parameter.
* */
open fun internReadFile(file: String, ioSystem: IOSystem = this.ioSystem, scene: AiScene) = Unit//internReadFile(file.uri, scene)

//open fun internReadFile(file: URI, pIOHandler: IOSystem, scene: AiScene) = Unit
open fun internReadFile(file: String, ioSystem: IOSystem, scene: AiScene) = Unit

companion object {
/** Extract file extension from a string
Expand Down
41 changes: 28 additions & 13 deletions src/main/kotlin/assimp/DefaultIOSystem.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
package assimp

import java.io.*
import java.nio.file.Files
import java.nio.*
import java.nio.channels.*
import java.nio.file.Path
import java.nio.file.Paths

class DefaultIOSystem : IOSystem {

override fun exists(pFile: String) = File(pFile).exists()
override fun exists(file: String) = File(file).exists()

override fun open(pFile: String): IOStream {
override fun open(file: String): IOStream {

val path: Path = Paths.get(pFile)
if (!Files.exists(path))
throw IOException("File doesn't exist: $pFile")
val path: Path = Paths.get(file)
if (!exists(file))
throw IOException("File doesn't exist: $file")


return FileIOStream(path)
return FileIOStream(path, this)
}

class FileIOStream(override val path: Path) : IOStream {
class FileIOStream(private val pathObject: Path, override val osSystem: DefaultIOSystem) : IOStream {

override val path: String
get() = pathObject.toString()

override fun read() = FileInputStream(path.toFile())
override fun read() = FileInputStream(file)

override fun reader() = BufferedReader(FileReader(path.toFile()))
override fun reader() = BufferedReader(FileReader(file))

override val filename: String
get() = path.fileName.toString()
get() = pathObject.fileName.toString()

override val parentPath = pathObject.parent.toAbsolutePath().toString()

override val length: Long
get() = file.length()

override fun readBytes(): ByteBuffer {
RandomAccessFile(file, "r").channel.use {fileChannel ->
return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()).order(ByteOrder.nativeOrder())
}
}

override fun parentPath() = path.parent.toAbsolutePath().toString()
val file: File
get() = pathObject.toFile()
}
}
19 changes: 16 additions & 3 deletions src/main/kotlin/assimp/IOStream.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,30 @@ package assimp

import java.io.BufferedReader
import java.io.InputStream
import java.nio.file.Path
import java.nio.*

interface IOStream {

val path : Path?
val path : String

val filename: String

fun read() : InputStream

fun reader() : BufferedReader

fun parentPath() : String
val parentPath : String

/**
* length of the IOStream in bytes
*/
val length: Long

/**
* reads the ioStream into a byte buffer.
* The byte order of the buffer is be [ByteOrder.nativeOrder].
*/
fun readBytes(): ByteBuffer

val osSystem: IOSystem
}
6 changes: 3 additions & 3 deletions src/main/kotlin/assimp/IOSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import java.io.File
/** Interface to the file system. */
interface IOSystem {

fun exists(pFile: String): Boolean
fun exists(file: String): Boolean

fun open(pFile : String): IOStream
fun open(file : String): IOStream

fun close(ioStream: IOStream) = Unit // TODO unused ?

fun getOsSeperator(): String = File.separator
val osSeparator: String get() = File.separator
}
75 changes: 69 additions & 6 deletions src/main/kotlin/assimp/Importer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import assimp.postProcess.ValidateDSProcess
import glm_.BYTES
import glm_.i
import glm_.size
import java.io.File
import java.net.URI
import java.net.URL
import java.nio.ByteBuffer
Expand Down Expand Up @@ -303,7 +302,7 @@ constructor() {
}

// Get file size for progress handler
val fileSize = File(file).length().i
val fileSize = ioSystem.open(file).length.i

// Dispatch the reading to the worker class for this format
val desc = imp.info
Expand Down Expand Up @@ -373,8 +372,7 @@ constructor() {
*
* @note This is a straightforward way to decode models from memory buffers, but it doesn't handle model formats
* that spread their data across multiple files or even directories. Examples include OBJ or MD3, which outsource
* parts of their material info into external scripts. If you need full functionality, provide a custom IOSystem
* to make Assimp find these files and use the regular readFile() API.
* parts of their material info into external scripts. If you need full functionality you can use [readFilesFromMemory]
*/
fun readFileFromMemory(buffer: ByteBuffer, flags: Int, hint: String = ""): AiScene? {
if (buffer.size == 0 || hint.length > MaxLenHint) {
Expand All @@ -385,16 +383,81 @@ constructor() {
// prevent deletion of previous IOSystem
val io = impl.ioSystem

ioHandler = MemoryIOSystem(buffer)

val fileName = "$AI_MEMORYIO_MAGIC_FILENAME.$hint"
ioHandler = MemoryIOSystem(fileName to buffer)

readFile(fileName, flags)

impl.ioSystem = io

return impl.scene
}

/**Reads the given file from a memory buffer and returns its contents if successful.
*
* If the call succeeds, the contents of the file are returned as a pointer to an AiScene object. The returned data
* is intended to be read-only, the importer object keeps ownership of the data and will destroy it upon
* destruction. If the import fails, null is returned.
* A human-readable error description can be retrieved by accessing errorString. The previous scene will be deleted
* during this call.
* Calling this method doesn't affect the active IOSystem.
*
* @param fileName name of the base file
* @param files a map containing the names and all the files required to read the scene (base file, materials,
* textures, etc).
* @param flags Optional post processing steps to be executed after a successful import. Provide a bitwise
* combination of the AiPostProcessSteps flags. If you wish to inspect the imported scene first in order to
* fine-tune your post-processing setup, consider to use applyPostProcessing().
* @return A pointer to the imported data, null if the import failed.
* The pointer to the scene remains in possession of the Importer instance. Use getOrphanedScene() to take
* ownership of it.
*/
fun readFilesFromMemory(fileName: String, files: Map<String, ByteBuffer>, flags: Int): AiScene? {

for((name, buffer) in files) {
if(buffer.size == 0){
impl.errorString = "buffer $name is empty"
return null
}
}
if(!files.containsKey(fileName)){
impl.errorString = "fileName ($fileName) not in files"
return null
}

val io = impl.ioSystem

ioHandler = MemoryIOSystem(files)

readFile(fileName, flags)

impl.ioSystem = io

return impl.scene
}

/**Reads the given file from a memory buffer and returns its contents if successful.
*
* If the call succeeds, the contents of the file are returned as a pointer to an AiScene object. The returned data
* is intended to be read-only, the importer object keeps ownership of the data and will destroy it upon
* destruction. If the import fails, null is returned.
* A human-readable error description can be retrieved by accessing errorString. The previous scene will be deleted
* during this call.
* Calling this method doesn't affect the active IOSystem.
*
* @param fileName name of the base file
* @param flags Optional post processing steps to be executed after a successful import. Provide a bitwise
* combination of the AiPostProcessSteps flags. If you wish to inspect the imported scene first in order to
* fine-tune your post-processing setup, consider to use applyPostProcessing().
* @param files the files required to read the scene (base file, materials, textures, etc) as a pair with their name.
* @return A pointer to the imported data, null if the import failed.
* The pointer to the scene remains in possession of the Importer instance. Use getOrphanedScene() to take
* ownership of it.
*/
fun readFilesFromMemory(fileName: String, vararg files: Pair<String, ByteBuffer>, flags: Int = 0): AiScene? {
return readFilesFromMemory(fileName, files.toMap(), flags)
}

/** Apply post-processing to an already-imported scene.
*
* This is strictly equivalent to calling readFile() with the same flags. However, you can use this separate
Expand Down
53 changes: 38 additions & 15 deletions src/main/kotlin/assimp/MemoryIOWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,66 @@ package assimp
import glm_.*
import java.io.*
import java.nio.*
import java.nio.file.*
import java.io.IOException



const val AI_MEMORYIO_MAGIC_FILENAME = "\$\$\$___magic___\$\$\$"
const val AI_MEMORYIO_MAGIC_FILENAME_LENGTH = 17

class MemoryIOSystem(val buffer: ByteBuffer) : IOSystem{
class MemoryIOSystem : IOSystem{

/** Tests for the existence of a file at the given path. */
override fun exists(pFile: String): Boolean = pFile.startsWith(AI_MEMORYIO_MAGIC_FILENAME)
val memoryFiles: MutableMap<String, ByteBuffer> = hashMapOf()

constructor(buffer: ByteBuffer) {
memoryFiles[AI_MEMORYIO_MAGIC_FILENAME] = buffer
}

constructor(vararg buffers: Pair<String, ByteBuffer>): this(buffers.toMap())

constructor(buffers: Map<String, ByteBuffer>){
memoryFiles.putAll(buffers)
}

/** Tests for the existence of a file at the given path. */
override fun exists(file: String): Boolean = memoryFiles.containsKey(file)

override fun open(pFile: String): IOStream {
override fun open(file: String): IOStream {

// TODO assimp originally returns null, but this would be against the current interface.
// I guess it should never happen anyways so an exception is fine
if(!pFile.startsWith(AI_MEMORYIO_MAGIC_FILENAME)) throw IOException("File does not exist! $pFile")
val buffer = memoryFiles[file] ?: throw IOException("File does not exist! $file")

return MemoryIOStream(buffer, pFile)
return MemoryIOStream(buffer, file, this)
}

class MemoryIOStream(val buffer: ByteBuffer, override val filename: String = "") : IOStream {
class MemoryIOStream(val buffer: ByteBuffer, override val path: String, override val osSystem: MemoryIOSystem) : IOStream {

override val path: Path?
get() = null
override val filename: String = run {
val lastIndex = path.lastIndexOf(osSystem.osSeparator)
path.substring(lastIndex + 1)
}

override fun read(): InputStream {
return ByteBufferBackedInputStream(buffer)
return ByteBufferBackedInputStream(readBytes())
}

override fun reader(): BufferedReader {
return BufferedReader(InputStreamReader(read()))
}

override fun parentPath(): String = ""
override val parentPath: String = run {
var parent = path.removeSuffix(filename)
parent = parent.removeSuffix(osSystem.osSeparator)

// ensures that if the path starts with "./" it will always be at least that
if(parent == ".") parent = ".${osSystem.osSeparator}"

parent
}

override val length: Long
get() = buffer.size.toLong()

override fun readBytes(): ByteBuffer = buffer.duplicate().order(ByteOrder.nativeOrder())
}
}

Expand All @@ -63,7 +86,7 @@ private class ByteBufferBackedInputStream(val buf: ByteBuffer) : InputStream() {
override fun read(): Int {
return if (!buf.hasRemaining()) {
-1
} else (buf.get() and 0xFF).toInt()
} else buf.get().toInt() and 0xFF
}

/**
Expand Down
11 changes: 5 additions & 6 deletions src/main/kotlin/assimp/format/X/XFileImporter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package assimp.format.X

import assimp.*
import assimp.AiFace
import java.io.File
import java.io.*

class XFileImporter : BaseImporter() {

Expand All @@ -24,16 +24,15 @@ class XFileImporter : BaseImporter() {
}

override fun internReadFile(file: String, ioSystem: IOSystem, scene: AiScene) {
// Read file into memory
val file_ = File(file)
if (!file_.canRead()) throw FileSystemException(file_, null, "Failed to open file \$pFile.")

val stream = ioSystem.open(file).read()
val bytes = stream.readBytes()

// Get the file-size and validate it, throwing an exception when fails
val fileSize = file_.length()
val fileSize = bytes.size

if (fileSize < 16) throw Error("XFile is too small.")

val bytes = file_.readBytes()
mBuffer = Pointer<Char>(Array<Char>(bytes.size, { i -> bytes[i].toChar() })) //Assuming every byte is a char.
// parse the file into a temporary representation
val parser = XFileParser(mBuffer)
Expand Down
Loading