From 9047d03883b2efb66c2072603b31a60befb34568 Mon Sep 17 00:00:00 2001 From: hYdos Date: Sun, 23 Feb 2020 10:18:11 +1000 Subject: [PATCH] "Y e S" --- .../java/io/github/hydos/ginger/Example.java | 4 +- .../hydos/ginger/engine/obj/Material.java | 5 + .../hydos/ginger/engine/obj/ModelData.java | 5 - .../hydos/ginger/engine/obj/ModelLoader.java | 2 +- .../ginger/engine/obj/OBJFileLoader.java | 161 +++++++++--------- .../hydos/ginger/engine/obj/Vertex.java | 27 +-- .../io/github/hydos/ginger/Example.class | Bin 11125 -> 11103 bytes .../hydos/ginger/engine/obj/ModelData.class | Bin 1150 -> 1006 bytes .../hydos/ginger/engine/obj/ModelLoader.class | Bin 1470 -> 1479 bytes .../ginger/engine/obj/OBJFileLoader.class | Bin 1902 -> 4877 bytes .../hydos/ginger/engine/obj/Vertex.class | Bin 2022 -> 2639 bytes 11 files changed, 105 insertions(+), 99 deletions(-) create mode 100644 src/main/java/io/github/hydos/ginger/engine/obj/Material.java diff --git a/src/main/java/io/github/hydos/ginger/Example.java b/src/main/java/io/github/hydos/ginger/Example.java index 469e07e..9043fdd 100644 --- a/src/main/java/io/github/hydos/ginger/Example.java +++ b/src/main/java/io/github/hydos/ginger/Example.java @@ -54,7 +54,7 @@ public class Example { public void main(String[] args) { - Window.create(800, 1200, "Ginger Example", 60); + Window.create(1200, 800, "Ginger Example", 60); GingerMain.init(); @@ -75,7 +75,7 @@ public class Example { FontType font = new FontType(Loader.loadFontAtlas("candara.png"), "candara.fnt"); - GUIText text = new GUIText("hi, this is some sample text", 3, font, new Vector2f(0,0), 1f, true); + GUIText text = new GUIText("german", 3, font, new Vector2f(0,0), 1f, true); text.setColour(0, 1, 0); text.setBorderWidth(0.7f); text.setBorderEdge(0.4f); diff --git a/src/main/java/io/github/hydos/ginger/engine/obj/Material.java b/src/main/java/io/github/hydos/ginger/engine/obj/Material.java new file mode 100644 index 0000000..e1bfc4a --- /dev/null +++ b/src/main/java/io/github/hydos/ginger/engine/obj/Material.java @@ -0,0 +1,5 @@ +package io.github.hydos.ginger.engine.obj; + +public class Material { + +} diff --git a/src/main/java/io/github/hydos/ginger/engine/obj/ModelData.java b/src/main/java/io/github/hydos/ginger/engine/obj/ModelData.java index bea9569..7b1a9ba 100644 --- a/src/main/java/io/github/hydos/ginger/engine/obj/ModelData.java +++ b/src/main/java/io/github/hydos/ginger/engine/obj/ModelData.java @@ -1,7 +1,5 @@ package io.github.hydos.ginger.engine.obj; -import org.lwjgl.assimp.AIScene; - public class ModelData { private float[] vertices; @@ -19,9 +17,6 @@ public class ModelData { this.furthestPoint = furthestPoint; } - public ModelData(AIScene scene) { - - } public float[] getVertices() { return vertices; diff --git a/src/main/java/io/github/hydos/ginger/engine/obj/ModelLoader.java b/src/main/java/io/github/hydos/ginger/engine/obj/ModelLoader.java index 76728db..bd3fbb2 100644 --- a/src/main/java/io/github/hydos/ginger/engine/obj/ModelLoader.java +++ b/src/main/java/io/github/hydos/ginger/engine/obj/ModelLoader.java @@ -7,7 +7,7 @@ import io.github.hydos.ginger.engine.utils.Loader; public class ModelLoader { public static TexturedModel loadModel(String objPath, String texturePath) { - ModelData data = OBJFileLoader.loadOBJ(objPath); + ModelData data = OBJFileLoader.loadModel(objPath, texturePath); TexturedModel tm = new TexturedModel(Loader.loadToVAO(data.getVertices(), data.getIndices(), data.getNormals(), data.getTextureCoords()), new ModelTexture(texturePath)); return tm; } diff --git a/src/main/java/io/github/hydos/ginger/engine/obj/OBJFileLoader.java b/src/main/java/io/github/hydos/ginger/engine/obj/OBJFileLoader.java index 31ef57d..0197bfe 100644 --- a/src/main/java/io/github/hydos/ginger/engine/obj/OBJFileLoader.java +++ b/src/main/java/io/github/hydos/ginger/engine/obj/OBJFileLoader.java @@ -1,93 +1,94 @@ package io.github.hydos.ginger.engine.obj; -import static org.lwjgl.assimp.Assimp.aiGetErrorString; -import static org.lwjgl.assimp.Assimp.aiImportFileEx; -import static org.lwjgl.assimp.Assimp.aiProcess_JoinIdenticalVertices; -import static org.lwjgl.assimp.Assimp.aiProcess_Triangulate; -import static org.lwjgl.system.MemoryUtil.NULL; -import static org.lwjgl.system.MemoryUtil.memAddress; -import static org.lwjgl.system.MemoryUtil.memCopy; -import static org.lwjgl.system.MemoryUtil.memUTF8; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import org.lwjgl.assimp.AIFile; -import org.lwjgl.assimp.AIFileCloseProc; -import org.lwjgl.assimp.AIFileCloseProcI; -import org.lwjgl.assimp.AIFileIO; -import org.lwjgl.assimp.AIFileOpenProc; -import org.lwjgl.assimp.AIFileOpenProcI; -import org.lwjgl.assimp.AIFileReadProc; -import org.lwjgl.assimp.AIFileReadProcI; -import org.lwjgl.assimp.AIFileSeek; -import org.lwjgl.assimp.AIFileSeekI; -import org.lwjgl.assimp.AIFileTellProc; -import org.lwjgl.assimp.AIFileTellProcI; +import org.lwjgl.assimp.AIFace; +import org.lwjgl.assimp.AIMesh; import org.lwjgl.assimp.AIScene; +import org.lwjgl.assimp.AIVector3D; +import org.lwjgl.assimp.AIVector3D.Buffer; import org.lwjgl.assimp.Assimp; -import io.github.hydos.ginger.engine.render.tools.IOUtil; +import io.github.hydos.ginger.engine.math.vectors.Vector2f; +import io.github.hydos.ginger.engine.math.vectors.Vector3f; public class OBJFileLoader { - private static final String RES_LOC = "/models/"; + public static String resourceLocation = "/models/"; + + public static ModelData loadModel(String filePath, String texturePath) { + AIScene scene = null; + try { + scene = Assimp.aiImportFile(resourceLocation + filePath, Assimp.aiProcess_JoinIdenticalVertices | Assimp.aiProcess_Triangulate); + + AIMesh mesh = AIMesh.create(scene.mMeshes().get(0)); + int vertexCount = mesh.mNumVertices(); + + AIVector3D.Buffer vertices = mesh.mVertices(); + AIVector3D.Buffer normals = mesh.mNormals(); + + Vertex[] vertexList = new Vertex[vertexCount]; + + for (int i = 0; i < vertexCount; i++) { + AIVector3D vertex = vertices.get(i); + Vector3f meshVertex = new Vector3f(vertex.x(), vertex.y(), vertex.z()); + + AIVector3D normal = normals.get(i); + Vector3f meshNormal = new Vector3f(normal.x(), normal.y(), normal.z()); + + Vector2f meshTextureCoord = new Vector2f(0, 0); + if (mesh.mNumUVComponents().get(0) != 0) { + AIVector3D texture = mesh.mTextureCoords(0).get(i); + meshTextureCoord.setX(texture.x()); + meshTextureCoord.setY(texture.y()); + } + + vertexList[i] = new Vertex(meshVertex, meshNormal, meshTextureCoord); + } + + int faceCount = mesh.mNumFaces(); + AIFace.Buffer indices = mesh.mFaces(); + int[] indicesList = new int[faceCount * 3]; + + for (int i = 0; i < faceCount; i++) { + AIFace face = indices.get(i); + indicesList[i * 3 + 0] = face.mIndices().get(0); + indicesList[i * 3 + 1] = face.mIndices().get(1); + indicesList[i * 3 + 2] = face.mIndices().get(2); + } + + return parseMeshData(vertexList, indicesList, normals); + }catch(Exception e) { + System.err.println("Couldnt load scene file!"); + e.printStackTrace(); + } + + + return new ModelData(new float[0], new float[0], new float[0], new int[0], 1F); + } - public static ModelData loadOBJ(String objFileName) { - AIFileIO fileIo = AIFileIO.create(); - AIFileOpenProcI fileOpenProc = new AIFileOpenProc() { - public long invoke(long pFileIO, long fileName, long openMode) { - AIFile aiFile = AIFile.create(); - final ByteBuffer data; - String fileNameUtf8 = memUTF8(fileName); - try { - data = IOUtil.ioResourceToByteBuffer(fileNameUtf8, 8192); - } catch (IOException e) { - throw new RuntimeException("Could not open file: " + fileNameUtf8); - } - AIFileReadProcI fileReadProc = new AIFileReadProc() { - public long invoke(long pFile, long pBuffer, long size, long count) { - long max = Math.min(data.remaining(), size * count); - memCopy(memAddress(data) + data.position(), pBuffer, max); - return max; - } - }; - AIFileSeekI fileSeekProc = new AIFileSeek() { - public int invoke(long pFile, long offset, int origin) { - if (origin == Assimp.aiOrigin_CUR) { - data.position(data.position() + (int) offset); - } else if (origin == Assimp.aiOrigin_SET) { - data.position((int) offset); - } else if (origin == Assimp.aiOrigin_END) { - data.position(data.limit() + (int) offset); - } - return 0; - } - }; - AIFileTellProcI fileTellProc = new AIFileTellProc() { - public long invoke(long pFile) { - return data.limit(); - } - }; - aiFile.ReadProc(fileReadProc); - aiFile.SeekProc(fileSeekProc); - aiFile.FileSizeProc(fileTellProc); - return aiFile.address(); - } - }; - AIFileCloseProcI fileCloseProc = new AIFileCloseProc() { - public void invoke(long pFileIO, long pFile) { - /* Nothing to do */ - } - }; - fileIo.set(fileOpenProc, fileCloseProc, NULL); - AIScene scene = aiImportFileEx(RES_LOC+objFileName, - aiProcess_JoinIdenticalVertices | aiProcess_Triangulate, fileIo); - if (scene == null) { - throw new IllegalStateException(aiGetErrorString()); - } - return new ModelData(scene); + private static ModelData parseMeshData(Vertex[] vertexList, int[] indicesList, Buffer normals) { + float[] verticies = new float[vertexList.length]; + float[] textureCoords = new float[vertexList.length]; + //texture coords where stored in the vertices so there should be as many as there are vertices + + int j = 0; + int i = 0; + for(Vertex vertex: vertexList) { + float x = vertex.getPosition().x; + float y = vertex.getPosition().y; + float z = vertex.getPosition().z; + verticies[i] = x; + i++; + verticies[i] = y; + i++; + verticies[i] = z; + i++; + textureCoords[j] = vertex.getTextureIndex().x; + j++; + textureCoords[j] = vertex.getTextureIndex().y; + } + + return new ModelData(verticies, textureCoords, null, indicesList, i); } } \ No newline at end of file diff --git a/src/main/java/io/github/hydos/ginger/engine/obj/Vertex.java b/src/main/java/io/github/hydos/ginger/engine/obj/Vertex.java index 2c13bf1..e5bd42d 100644 --- a/src/main/java/io/github/hydos/ginger/engine/obj/Vertex.java +++ b/src/main/java/io/github/hydos/ginger/engine/obj/Vertex.java @@ -1,14 +1,13 @@ package io.github.hydos.ginger.engine.obj; +import io.github.hydos.ginger.engine.math.vectors.Vector2f; import io.github.hydos.ginger.engine.math.vectors.Vector3f; public class Vertex { - - private static final int NO_INDEX = -1; - + private Vector3f position; - private int textureIndex = NO_INDEX; - private int normalIndex = NO_INDEX; + private Vector2f textureIndex = null; + private Vector3f normalIndex = null; private Vertex duplicateVertex = null; private int index; private float length; @@ -19,6 +18,12 @@ public class Vertex { this.length = position.length(); } + public Vertex(Vector3f position, Vector3f normal, Vector2f textureCoord) { + this.position = position; + this.normalIndex = normal; + this.textureIndex = textureCoord; + } + public int getIndex(){ return index; } @@ -28,18 +33,18 @@ public class Vertex { } public boolean isSet(){ - return textureIndex!=NO_INDEX && normalIndex!=NO_INDEX; + return textureIndex!=null && normalIndex!=null; } - public boolean hasSameTextureAndNormal(int textureIndexOther,int normalIndexOther){ + public boolean hasSameTextureAndNormal(Vector2f textureIndexOther,Vector3f normalIndexOther){ return textureIndexOther==textureIndex && normalIndexOther==normalIndex; } - public void setTextureIndex(int textureIndex){ + public void setTextureIndex(Vector2f textureIndex){ this.textureIndex = textureIndex; } - public void setNormalIndex(int normalIndex){ + public void setNormalIndex(Vector3f normalIndex){ this.normalIndex = normalIndex; } @@ -47,11 +52,11 @@ public class Vertex { return position; } - public int getTextureIndex() { + public Vector2f getTextureIndex() { return textureIndex; } - public int getNormalIndex() { + public Vector3f getNormalIndex() { return normalIndex; } diff --git a/target/classes/io/github/hydos/ginger/Example.class b/target/classes/io/github/hydos/ginger/Example.class index 09cbe879ccf31f8dacae9bc4cf6281cf228da360..c5af99cdf1a919c6ff449d2979ee69a675d650fd 100644 GIT binary patch delta 43 zcmewwc0X*xYfgT)^wgr<#5@)U2L=XqhG~;OvYKpm;nJ35W!WIetgyL6nUMnkRTvEy delta 65 zcmcZ~_BCw7Yfe>}j7%Mcl8nq^1t2QU&rMY*PRuRHNmVFGtteq}aA06yXP7qmBdf_~ TJuYoYR%Qi3mJOTZlo>ey6;Bk& diff --git a/target/classes/io/github/hydos/ginger/engine/obj/ModelData.class b/target/classes/io/github/hydos/ginger/engine/obj/ModelData.class index ad34bf9693fda9a400d9242e64d9e72d9b11cd8e..8e4d8d1a488a269bea5e381c1b9e43801c50d2d1 100644 GIT binary patch delta 226 zcmeyz@s3^K)W2Q(7#J8#7_`|Lm>3zfCmS+qZ?slnoV0$vL~~Oq!@!7&>S8HMj*)wlvM!Ij10;^**2i8 s6joUkpzL9wtQ=NZRR*<5EGm*pSS8gNG$z}!sK}~emDL1V&cvVv04@3#@c;k- delta 369 zcmaFI{*Oc8)W2Q(7#J8#81&c~m>3zfCmS+qZ?slnbduHZ$uCOR&neGJ&(TjTF3!v? z(0B9FNOFM%#ejxLFvtPUe-6AX$hyMg}#Y>^GpSJXTqCpsX>oilj1D gSq%ow$pt`Jb*!>lz>r(ZtRkz8RaOV+3MK|!061kbEdT%j diff --git a/target/classes/io/github/hydos/ginger/engine/obj/ModelLoader.class b/target/classes/io/github/hydos/ginger/engine/obj/ModelLoader.class index 124ad82ba57d2f26b6b1ad85b1064b90058f6d95..6cc9c672c2cca147e59161c5e75e0968589d03ed 100644 GIT binary patch delta 419 zcmYLFOG^S#7(Lf9r^Z2CXqmkq=8U6G4||#t^Z-GHFa&8e8vSz4OM zw4LE3d(82nEB)T;n0mEfRQ0BS5W+lW8I+zs+I7Mr<}lCTt?5=-H?8`O-ez!1^7Vxi zUKFu}Wd?!lT&w!;<{Wob#F|rf+@fLLYR$GIM?`EmvY+I?>rV~Ctdc1q#zSU^)eW`Q z3xw?tmyc5rx4&GMEJ=^fstIh0n7|f8P|7#7J56nBt(sc0%zCSqmdgTy*l`jm220A@ zCtT!t%r#i|0Uo&NUzC_KfWI(Auo8d43}Jz=LRcRlIz*zP^r7~#T}(Vu+y#Ny2aZ^v o6<){?iGkSfI_zSPCR&VrIx)&baDYQ<%7i1Nsms70Q^bYL5BEP(!2kdN delta 362 zcmXAkPfG$(6vfYbGda`D5C%1+m6m0V|E(<3{u@LD89@ZnN^*!m85#|uO&_3DycT_l zpa@bDgjPXcr+st>7w7WMeZPCS_gnAj{-3|k9)KLS6>x@>c$7n8hY!Bx57_VsMj4d4 zQ>`7B4jFbW@ytr{7(;=uf~p~a2?aqB=Gn+3rWo`~yLD!}t*Z;W$sk*?^L-Br8<@t7 zxaMXtfLQ}`9;*>scHBm_-t?Gx0}Ec;Pwi9twsqs$dyeDQ=re|dg1GqN87WB{{_+z! z)88Q~8j`7|v1nibOX5KaswQ%tS$Thvp4j~ze30qBc)SDuQ5R+<`GIj#h!i13JFvP) zR}yVx+gL8AUWw+=s18A*s!^B@3kFrn>8D{8d2*g#qZcD4iUNu>X_3~kL9+~-K0-LQ F{sFqPKu-Vw diff --git a/target/classes/io/github/hydos/ginger/engine/obj/OBJFileLoader.class b/target/classes/io/github/hydos/ginger/engine/obj/OBJFileLoader.class index d1d330ff74427319210f96f86c3d18cbacb63cc2..1d618133fde2fec20d759bf99cd66099fc1f8875 100644 GIT binary patch literal 4877 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 literal 1902 zcmbVNVOJYP6ukop3(Gb|q%D+|mbSD3N>?gsODYO!S{Flt4NZ$*Jey?CfUStXk-E)06UK1G1&Q}Fed2Z+! zM8<%QQGwG%aw)YMH5u;MHBYKT0>}2McIc}0%ZI6W-;D&8i*Fed`Dq@bW1s@-*^~U| z|AWUZ70i7*wsShpVp797fvF&@o8I>abc?Rx;(vBI=#q4~s zFF2>+0>gUCsID#Rm_|m!rT<}jTgMDCDv~P(uHqU?6G@iY$@ZgJZc2YU3>+)h50UqR zNG6cYe~$LLz_~X?w9#@s>QBcUvPu^70%v-as;Ur+25w@B`L&y*wI{ID3wol^FVA2F z=U^Im56hHk93(0hm|&6Kq($ue_&~!7DW5E0x9A2w#76=d+qD|aAdHm)3eN<#|IR7D zZ-*86ZAFDa9|0+)^%TAnBCwpWQMvBEP)He)yN72lkJdn&du z+kGhGLKp^N5~Geg$ZGgRVDaD8cBFV6Ygkt`*-%36#XaGt?)E4XZ zEIbFlx5o6vL)>0XO{IQArh_}Xhgfan<6k>i+nq|a@#&BBI0(gJfkc7pNZ|(0EA!kP zT)<;q{3?k*<{Rz_?&2x#R%%+Qi!NF8kE;5eJ0oXerj2AIHgg}o=Xbt)&bdea`=3Am0&ovIBS~-D1^*Rdo_x>N&wu7eil2)GIB~`}NZyzZb#0Nd!+3hr(S3_a$3TCY1`E9!uQ6?L` zO|RkP zkw($L6fP^ID@zBXP?$C_f(tUcVqg?GnO!xI$C%8n889#|vyTj9k;~&_%#YxP!s-x_ zH!y((?RrO$H)TDkFuBLXKkKz?j{m~0wH$@Qo>#Y92e$9ZI@-(xO}DG?Q)Q?id>|v% zg@ij7R>o&*QM+}IjyDAjYdo@prs2JIcj z%6UnMAtYM)eXr-&ogG&m=2+O;Zb?^WD`2g%v*(bK-oRN(dWjy{N%Y7*qDOWSJ@UbG z9H-?JF7jL8>Id2h=cf4wE}7~CCHa))Gh<#o!K_(5!F99xJLXQX_^al8!tV|3YZ4ju ztxMS5w}8_ zgHkRJNGVauG^La&^@^5ahUmVOc`ZPs%wt(gky(`&OH^cDt(@>^sxZ_*+o9h0#T zBNZYUcl8-HMZ_xq0fc(KiSk`s7UpY=R|Wby5n9pEFoVT@*owH%5Q(fJKGk8192L0v z0Bez>4LVBkP|jrDV%f^;-6~_hqVc8hR3D$yWi0o8h|iK(<an+s_n62C1SsO^6NDUU1* literal 2022 zcmbVM?QRoC6g}fll59ROh8QP6fI>DK!?sOnprHX8n#xj|AO%?nexS8I#2cL5XuU@8 zuWwQw09CDoTd6;I03NFCncXSbn1x8OD#0)^YI=Z32S zx6JwxVCkF}8UndBzb!|R#Tf%7ObcYqg~kX3&Kej)L3uLp-FcEP}89J^>> z1f!$4j4MTaA+U6e?irZE)u{Bk0yowAq`>3`1>WrMHD&P7X?CSRX~Slj3fL9T{-c5(?|kWKV_~L{rI`b=BqxzKAh(e2nBCe3-dqbKzJDvPZ-@ zTycVtL#0X#+#epl+L09x4Pc4M>I{840Mc$#|{@r2!?nVe7YoYF|>fgPnQ9t|# DnK^B-