This is my Clojure code:
(ns learnopengl.mesh-model
(:import [org.lwjgl.assimp Assimp AINode AIMesh]))
(defn read-model
"read a 3D model from a file"
[path]
(let [scene (Assimp/aiImportFile path (bit-or Assimp/aiProcess_Triangulate
Assimp/aiProcess_FlipUVs))]
(if (= scene nil)
(println (Assimp/aiGetErrorString))
(for [index (range (.mNumMeshes scene))]
(do
(println index)
(AIMesh/create (.get (.mMeshes scene) index)))))))
which fails with:
0
Execution error (IllegalArgumentException) at java.nio.Buffer/createCapacityException (Buffer.java:290).
capacity < 0: (-576307456 < 0)
This happens inside AIMesh/create.
Believing I may have encountered a bug, I rewrote the snippet in Java:
import org.lwjgl.assimp.Assimp;
import org.lwjgl.assimp.AIScene;
import org.lwjgl.assimp.AIMesh;
class Test {
public static void main(String[] args) {
AIScene scene = Assimp.aiImportFile("backpack/backpack.obj", Assimp.aiProcess_Triangulate);
if (scene == null) {
System.out.println(Assimp.aiGetErrorString());
} else {
for (int i = 0; i < scene.mNumMeshes(); i++) {
System.out.println(i);
AIMesh mesh = AIMesh.create(scene.mMeshes().get(i));
}
}
}
}
The Java code however runs smoothly. I don't understand why, since I believe the two snippets to be essentially the same. Can someone spot where I may have gone wrong in the Clojure code?
This is the 3D model I've been testing with.
Edit: This is my project.clj:
(defproject learnopengl "0.1.0-SNAPSHOT"
:description "learnopengl book code"
:url "learnopengl.com"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.11.1"]
[org.lwjgl/lwjgl "3.3.6"]
[org.lwjgl/lwjgl "3.3.6" :classifier "natives-linux"]
[org.lwjgl/lwjgl-opengl "3.3.6"]
[org.lwjgl/lwjgl-opengl "3.3.6" :classifier "natives-linux"]
[org.lwjgl/lwjgl-stb "3.3.6"]
[org.lwjgl/lwjgl-stb "3.3.6" :classifier "natives-linux"]
[org.lwjgl/lwjgl-glfw "3.3.6"]
[org.lwjgl/lwjgl-glfw "3.3.6" :classifier "natives-linux"]
[org.lwjgl/lwjgl-assimp "3.3.6"]
[org.lwjgl/lwjgl-assimp "3.3.6" :classifier "natives-linux"]
[org.joml/joml "1.10.8"]]
:main ^:skip-aot learnopengl.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all
:jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
Edit2:
This is my repository with the full code that breaks for me. read-model
is defined in mesh-model.clj, and it is called an line 11 in core.clj.
Edit3:
This is the content from the /tmp/clojure-xxx.edn file produced for the crash, which contains the stack trace:
{:clojure.main/message
"Execution error (IllegalArgumentException) at java.nio.Buffer/createCapacityException (Buffer.java:290).\ncapacity < 0: (-689151616 < 0)\n",
:clojure.main/triage
{:clojure.error/class java.lang.IllegalArgumentException,
:clojure.error/line 290,
:clojure.error/cause "capacity < 0: (-689151616 < 0)",
:clojure.error/symbol java.nio.Buffer/createCapacityException,
:clojure.error/source "Buffer.java",
:clojure.error/phase :execution},
:clojure.main/trace
{:via
[{:type clojure.lang.Compiler$CompilerException,
:message
"Syntax error macroexpanding at (learnopengl/core.clj:12:1).",
:data
{:clojure.error/phase :execution,
:clojure.error/line 12,
:clojure.error/column 1,
:clojure.error/source "learnopengl/core.clj"},
:at [clojure.lang.Compiler load "Compiler.java" 7665]}
{:type java.lang.IllegalArgumentException,
:message "capacity < 0: (-689151616 < 0)",
:at [java.nio.Buffer createCapacityException "Buffer.java" 290]}],
:trace
[[java.nio.Buffer createCapacityException "Buffer.java" 290]
[java.nio.Buffer <init> "Buffer.java" 253]
[java.nio.ByteBuffer <init> "ByteBuffer.java" 316]
[java.nio.ByteBuffer <init> "ByteBuffer.java" 324]
[java.nio.MappedByteBuffer <init> "MappedByteBuffer.java" 113]
[java.nio.DirectByteBuffer <init> "DirectByteBuffer.java" 107]
[java.nio.ByteBuffer allocateDirect "ByteBuffer.java" 360]
[org.lwjgl.system.Struct __create "Struct.java" 118]
[org.lwjgl.assimp.AIMesh create "AIMesh.java" 487]
[jdk.internal.reflect.DirectMethodHandleAccessor
invoke
"DirectMethodHandleAccessor.java"
103]
[java.lang.reflect.Method invoke "Method.java" 580]
[clojure.lang.Reflector invokeMatchingMethod "Reflector.java" 167]
[clojure.lang.Reflector invokeStaticMethod "Reflector.java" 332]
[learnopengl.mesh_model$read_model$iter__332__336$fn__337$fn__338
invoke
"mesh_model.clj"
66]
[learnopengl.mesh_model$read_model$iter__332__336$fn__337
invoke
"mesh_model.clj"
63]
[clojure.lang.LazySeq sval "LazySeq.java" 42]
[clojure.lang.LazySeq seq "LazySeq.java" 51]
[clojure.lang.RT seq "RT.java" 535]
[clojure.lang.RT countFrom "RT.java" 650]
[clojure.lang.RT count "RT.java" 643]
[learnopengl.core$eval351 invokeStatic "core.clj" 12]
[learnopengl.core$eval351 invoke "core.clj" 12]
[clojure.lang.Compiler eval "Compiler.java" 7194]
[clojure.lang.Compiler load "Compiler.java" 7653]
[clojure.lang.RT loadResourceScript "RT.java" 381]
[clojure.lang.RT loadResourceScript "RT.java" 372]
[clojure.lang.RT load "RT.java" 459]
[clojure.lang.RT load "RT.java" 424]
[clojure.core$load$fn__6908 invoke "core.clj" 6161]
[clojure.core$load invokeStatic "core.clj" 6160]
[clojure.core$load doInvoke "core.clj" 6144]
[clojure.lang.RestFn invoke "RestFn.java" 408]
[clojure.core$load_one invokeStatic "core.clj" 5933]
[clojure.core$load_one invoke "core.clj" 5928]
[clojure.core$load_lib$fn__6850 invoke "core.clj" 5975]
[clojure.core$load_lib invokeStatic "core.clj" 5974]
[clojure.core$load_lib doInvoke "core.clj" 5953]
[clojure.lang.RestFn applyTo "RestFn.java" 142]
[clojure.core$apply invokeStatic "core.clj" 669]
[clojure.core$load_libs invokeStatic "core.clj" 6016]
[clojure.core$load_libs doInvoke "core.clj" 6000]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.core$apply invokeStatic "core.clj" 669]
[clojure.core$require invokeStatic "core.clj" 6038]
[clojure.core$require doInvoke "core.clj" 6038]
[clojure.lang.RestFn invoke "RestFn.java" 408]
[user$eval140$fn__144 invoke "form-init15974239925031016013.clj" 1]
[user$eval140 invokeStatic "form-init15974239925031016013.clj" 1]
[user$eval140 invoke "form-init15974239925031016013.clj" 1]
[clojure.lang.Compiler eval "Compiler.java" 7194]
[clojure.lang.Compiler eval "Compiler.java" 7184]
[clojure.lang.Compiler load "Compiler.java" 7653]
[clojure.lang.Compiler loadFile "Compiler.java" 7591]
[clojure.main$load_script invokeStatic "main.clj" 475]
[clojure.main$init_opt invokeStatic "main.clj" 477]
[clojure.main$init_opt invoke "main.clj" 477]
[clojure.main$initialize invokeStatic "main.clj" 508]
[clojure.main$null_opt invokeStatic "main.clj" 542]
[clojure.main$null_opt invoke "main.clj" 539]
[clojure.main$main invokeStatic "main.clj" 664]
[clojure.main$main doInvoke "main.clj" 616]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.main main "main.java" 40]],
:cause "capacity < 0: (-689151616 < 0)",
:phase :execution}}
As cfrick has mentioned in a comment under your question, for
is unlikely to be suitable here unless the caller of read-model
consumes the whole collection.
The code below is how I'd write it. Note that I also added ^String
and ^long
to avoid reflection.
(defn read-model
"Reads a 3D model from a file and returns a vector of AIMesh pointers."
[^String path]
(if-let [scene (Assimp/aiImportFile path (bit-or Assimp/aiProcess_Triangulate
Assimp/aiProcess_FlipUVs))]
(mapv (fn [^long index]
(AIMesh/create (.get (.mMeshes scene) index)))
(range (.mNumMeshes scene)))
(throw (ex-info (Assimp/aiGetErrorString) {:path path}))))