├── .gitignore ├── LICENSE ├── README.md ├── cube.dae ├── cube.ogex ├── nimasset.nimble ├── nimasset ├── asset_types.nim ├── collada.nim ├── fbx.nim ├── obj.nim └── ogex.nim ├── teapot.fbx └── teapot.obj /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | nimasset/obj 3 | nimasset/asset_types 4 | nimasset/collada 5 | nimasset/ogex 6 | nimasset/fbx 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Rostyslav Dzinko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nimasset - assets loading library for Nim applications 2 | ====================================================== 3 | 4 | NimAsset is a library containing generic low- and high-level routines and 5 | objects for loading assets within Nim applications. 6 | 7 | Supported asset formats: 8 | 9 | * `.obj` - low-level loader of WaveFront OBJ files containing 3d models 10 | * `.dae` - Open COLLADA v. 1.4 scene loader with support of loading: 11 | * geometry 12 | * materials 13 | * animation 14 | * lighting 15 | 16 | ## Loading `obj` files 17 | 18 | OBJ files loader provides extremely low-level API that allows client code 19 | to cut in loading process, and perform flexible in-memory layout of 3D 20 | object data. 21 | 22 | Here's an example that just print parsed data into `stdout`. 23 | 24 | ```nim 25 | let 26 | loader: ObjLoader = new(ObjLoaderObj) 27 | f = open("teapot.obj") 28 | fs = newFileStream(f) 29 | 30 | proc addVertex(x, y, z: float) = 31 | echo "Vertex: ", x, " ", y, " ", z 32 | 33 | proc addTexture(u, v, w: float) = 34 | echo "Texture: ", u, v, w 35 | 36 | proc addFace(vi0, vi1, vi2, ti0, ti1, ti2, ni0, ni1, ni2: int) = 37 | echo "Face: ", vi0, " ", vi1, " ", vi2, " ", ti0, " ", ti1, " ", ti2, " ", ni0, " ", ni1, " ", ni2 38 | 39 | loadMeshData(loader, fs, addVertex, addTexture, addFace) 40 | ``` 41 | 42 | ## Loading `dae` files 43 | 44 | COLLADA files allow complex scenes to be saved in a single file, so it loads 45 | scene data into in-memory objects. 46 | 47 | ```nim 48 | let 49 | f = open("cube.dae") 50 | fs = newFileStream(f) 51 | loader = ColladaLoader.new 52 | 53 | let scene = loader.load(fs) 54 | ``` 55 | 56 | ## Special Thanks to Contributors 57 | 58 | * @yglukhov for JavaScript fixes 59 | * @bogdan-voevoda for implementation of most of COLLADA loader 60 | -------------------------------------------------------------------------------- /cube.dae: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | alorino 6 | Maya 7.0 | ColladaMaya v2.01 Jun 9 2006 at 16:08:19 | FCollada v1.11 7 | Collada Maya Export Options: bakeTransforms=0;exportPolygonMeshes=1;bakeLighting=0;isSampling=0; 8 | curveConstrainSampling=0;exportCameraAsLookat=0; 9 | exportLights=1;exportCameras=1;exportJointsAndSkin=1; 10 | exportAnimations=1;exportTriangles=0;exportInvisibleNodes=0; 11 | exportNormals=1;exportTexCoords=1;exportVertexColors=1;exportTangents=0; 12 | exportTexTangents=0;exportConstraints=0;exportPhysics=0;exportXRefs=1; 13 | dereferenceXRefs=0;cameraXFov=0;cameraYFov=1 14 | 15 | Copyright 2006 Sony Computer Entertainment Inc. 16 | Licensed under the SCEA Shared Source License, Version 1.0 (the 17 | "License"); you may not use this file except in compliance with the 18 | License. You may obtain a copy of the License at: 19 | http://research.scea.com/scea_shared_source_license.html 20 | Unless required by applicable law or agreed to in writing, software 21 | distributed under the License is distributed on an "AS IS" BASIS, 22 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | See the License for the specific language governing permissions and 24 | limitations under the License. 25 | 26 | 27 | 2006-06-21T21:23:22Z 28 | 2006-06-21T21:23:22Z 29 | 30 | Y_UP 31 | 32 | 33 | 34 | 35 | 36 | 37 | 37.8493 38 | 1 39 | 10 40 | 1000 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 37.8501 50 | 1 51 | 0.01 52 | 1000 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 1 1 1 63 | 1 64 | 0 65 | 0 66 | 67 | 68 | 69 | 1.000000 70 | 71 | 72 | 73 | 74 | 75 | 0.1 0.1 0.2 76 | 77 | 78 | 79 | 80 | 81 | 82 | 1 1 1 83 | 1 84 | 0 85 | 0 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 0 0 0 1 102 | 103 | 104 | 0 0 0 1 105 | 106 | 107 | 0.137255 0.403922 0.870588 1 108 | 109 | 110 | 0.5 0.5 0.5 1 111 | 112 | 113 | 16 114 | 115 | 116 | 0 0 0 1 117 | 118 | 119 | 0.5 120 | 121 | 122 | 0 0 0 1 123 | 124 | 125 | 1 126 | 127 | 128 | 0 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -50 50 50 50 50 50 -50 -50 50 50 -50 50 -50 50 -50 50 50 -50 -50 -50 -50 50 -50 -50 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 0 0 1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 0 0 1 0 0 1 0 0 -1 0 0 -1 0 0 -1 0 0 -1 0 -1 0 0 -1 0 0 -1 0 0 -1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 0 0 -1 0 0 -1 0 0 -1 0 0 -1 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 4 4 4 4 4 4 165 |

0 0 2 1 3 2 1 3 0 4 1 5 5 6 4 7 6 8 7 9 3 10 2 11 0 12 4 13 6 14 2 15 3 16 7 17 5 18 1 19 5 20 7 21 6 22 4 23

166 |
167 |
168 |
169 |
170 | 171 | 172 | 173 | -427.749 333.855 655.017 174 | 0 1 0 -33 175 | 1 0 0 -22.1954 176 | 0 0 1 0 177 | 178 | 179 | 180 | -500 1000 400 181 | 0 0 1 0 182 | 0 1 0 0 183 | 1 0 0 0 184 | 185 | 186 | 187 | 188 | 0 0 1 0 189 | 0 1 0 0 190 | 1 0 0 0 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | -427.749 333.855 655.017 201 | 0 1 0 -33 202 | 1 0 0 -22.1954 203 | 0 0 1 0 204 | 205 | 206 | 207 | 3 4 10 208 | 0 0 1 0 209 | 0 1 0 0 210 | 1 0 0 0 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 |
219 | -------------------------------------------------------------------------------- /cube.ogex: -------------------------------------------------------------------------------- 1 | Metric (key = "distance") {float {0.01}} 2 | Metric (key = "angle") {float {1}} 3 | Metric (key = "time") {float {1}} 4 | Metric (key = "up") {string {"z"}} 5 | 6 | GeometryNode $node1 7 | { 8 | Name {string {"Cube"}} 9 | ObjectRef {ref {$geometry1}} 10 | MaterialRef {ref {$material1}} 11 | 12 | Transform 13 | { 14 | float[16] 15 | { 16 | {0x3F800000, 0x00000000, 0x00000000, 0x00000000, // {1, 0, 0, 0 17 | 0x00000000, 0x3F800000, 0x00000000, 0x00000000, // 0, 1, 0, 0 18 | 0x00000000, 0x00000000, 0x3F800000, 0x00000000, // 0, 0, 1, 0 19 | 0x42480000, 0x42480000, 0x00000000, 0x3F800000} // 50, 50, 0, 1} 20 | } 21 | } 22 | } 23 | 24 | GeometryObject $geometry1 // Cube 25 | { 26 | Mesh (primitive = "triangles") 27 | { 28 | VertexArray (attrib = "position") 29 | { 30 | float[3] // 24 31 | { 32 | {0xC2480000, 0xC2480000, 0x00000000}, {0xC2480000, 0x42480000, 0x00000000}, 33 | {0x42480000, 0x42480000, 0x00000000}, {0x42480000, 0xC2480000, 0x00000000}, 34 | {0xC2480000, 0xC2480000, 0x42C80000}, {0x42480000, 0xC2480000, 0x42C80000}, 35 | {0x42480000, 0x42480000, 0x42C80000}, {0xC2480000, 0x42480000, 0x42C80000}, 36 | {0xC2480000, 0xC2480000, 0x00000000}, {0x42480000, 0xC2480000, 0x00000000}, 37 | {0x42480000, 0xC2480000, 0x42C80000}, {0xC2480000, 0xC2480000, 0x42C80000}, 38 | {0x42480000, 0xC2480000, 0x00000000}, {0x42480000, 0x42480000, 0x00000000}, 39 | {0x42480000, 0x42480000, 0x42C80000}, {0x42480000, 0xC2480000, 0x42C80000}, 40 | {0x42480000, 0x42480000, 0x00000000}, {0xC2480000, 0x42480000, 0x00000000}, 41 | {0xC2480000, 0x42480000, 0x42C80000}, {0x42480000, 0x42480000, 0x42C80000}, 42 | {0xC2480000, 0x42480000, 0x00000000}, {0xC2480000, 0xC2480000, 0x00000000}, 43 | {0xC2480000, 0xC2480000, 0x42C80000}, {0xC2480000, 0x42480000, 0x42C80000} 44 | } 45 | } 46 | 47 | VertexArray (attrib = "normal") 48 | { 49 | float[3] // 24 50 | { 51 | {0x00000000, 0x00000000, 0xBF800000}, {0x00000000, 0x00000000, 0xBF800000}, 52 | {0x00000000, 0x00000000, 0xBF800000}, {0x00000000, 0x00000000, 0xBF800000}, 53 | {0x00000000, 0x00000000, 0x3F800000}, {0x00000000, 0x00000000, 0x3F800000}, 54 | {0x00000000, 0x00000000, 0x3F800000}, {0x00000000, 0x00000000, 0x3F800000}, 55 | {0x00000000, 0xBF800000, 0x00000000}, {0x00000000, 0xBF800000, 0x00000000}, 56 | {0x00000000, 0xBF800000, 0x00000000}, {0x80000000, 0xBF800000, 0x00000000}, 57 | {0x3F800000, 0x00000000, 0x00000000}, {0x3F800000, 0x00000000, 0x00000000}, 58 | {0x3F800000, 0x00000000, 0x00000000}, {0x3F800000, 0x00000000, 0x00000000}, 59 | {0x00000000, 0x3F800000, 0x00000000}, {0x00000000, 0x3F800000, 0x00000000}, 60 | {0x00000000, 0x3F800000, 0x00000000}, {0x80000000, 0x3F800000, 0x00000000}, 61 | {0xBF800000, 0x00000000, 0x00000000}, {0xBF800000, 0x00000000, 0x00000000}, 62 | {0xBF800000, 0x00000000, 0x00000000}, {0xBF800000, 0x00000000, 0x00000000} 63 | } 64 | } 65 | 66 | VertexArray (attrib = "texcoord") 67 | { 68 | float[2] // 24 69 | { 70 | {0x3F800000, 0x00000000}, {0x3F800000, 0x3F800000}, {0x00000000, 0x3F800000}, 71 | {0x00000000, 0x00000000}, {0x00000000, 0x00000000}, {0x3F800000, 0x00000000}, 72 | {0x3F800000, 0x3F800000}, {0x00000000, 0x3F800000}, {0x00000000, 0x00000000}, 73 | {0x3F800000, 0x00000000}, {0x3F800000, 0x3F800000}, {0x00000000, 0x3F800000}, 74 | {0x00000000, 0x00000000}, {0x3F800000, 0x00000000}, {0x3F800000, 0x3F800000}, 75 | {0x00000000, 0x3F800000}, {0x00000000, 0x00000000}, {0x3F800000, 0x00000000}, 76 | {0x3F800000, 0x3F800000}, {0x00000000, 0x3F800000}, {0x00000000, 0x00000000}, 77 | {0x3F800000, 0x00000000}, {0x3F800000, 0x3F800000}, {0x00000000, 0x3F800000} 78 | } 79 | } 80 | 81 | IndexArray 82 | { 83 | unsigned_int32[3] // 12 84 | { 85 | {0, 1, 2}, {2, 3, 0}, {4, 5, 6}, {6, 7, 4}, {8, 9, 10}, 86 | {10, 11, 8}, {12, 13, 14}, {14, 15, 12}, {16, 17, 18}, 87 | {18, 19, 16}, {20, 21, 22}, {22, 23, 20} 88 | } 89 | } 90 | } 91 | } 92 | 93 | Material $material1 94 | { 95 | Name {string {"Green"}} 96 | 97 | Color (attrib = "diffuse") {float[3] {{0, 1, 0}}} 98 | } 99 | -------------------------------------------------------------------------------- /nimasset.nimble: -------------------------------------------------------------------------------- 1 | # `nimasset` package 2 | 3 | version = "0.1" 4 | author = "Rostyslav Dzinko " 5 | description = "Generic media assets loading library" 6 | license = "MIT" 7 | 8 | # Deps 9 | -------------------------------------------------------------------------------- /nimasset/asset_types.nim: -------------------------------------------------------------------------------- 1 | type 2 | MeshData*[V, F] = ref object of RootObj ## 3D Mesh-related data from *.obj files 3 | vertices*: seq[array[0..2, V]] ## - vertices array 4 | faces*: seq[array[0..2, F]] ## - faces array (vertices index array) 5 | 6 | 7 | proc newMeshData*[V, F](vertices: seq[array[0..2, V]], faces: seq[array[0..2, F]]): MeshData[V, F] = 8 | result.new 9 | result.vertices = vertices 10 | result.faces = faces 11 | 12 | proc newMeshData*[V, F](verticesCount: Natural, facesCount: Natural): MeshData[V, F] = 13 | ## MeshData reference type constructor. Takes number of vertices and 14 | ## number of faces for initializing internal storage. 15 | result.new 16 | result.vertices = newSeq[array[0..2, V]](verticesCount) 17 | result.faces = newSeq[array[0..2, F]](facesCount) 18 | 19 | proc `$`*[V, F](mesh: MeshData[V, F]): string = 20 | ## MeshData toString operator 21 | return "3D Mesh " & " (" & $(mesh.vertexCount) & " vertices, " & $(mesh.faceCount) & " faces)" 22 | 23 | proc vertexCount*(mesh: MeshData): Natural = 24 | return len(mesh.vertices) 25 | 26 | proc faceCount*(mesh: MeshData): Natural = 27 | return len(mesh.faces) 28 | 29 | when (isMainModule): 30 | let mesh = newMeshData[float32, int32](0, 0) 31 | echo mesh 32 | -------------------------------------------------------------------------------- /nimasset/collada.nim: -------------------------------------------------------------------------------- 1 | import streams 2 | import parsexml 3 | import times 4 | import strutils 5 | 6 | type 7 | ErrBadCollada* = ref object of Exception 8 | ## Bad-formed COLLADA error 9 | 10 | ColladaLoader* = ref object 11 | ## Loads COLLADA (*.dae) format for 3D assets 12 | 13 | ColladaMaterial* = object 14 | ## Material info deserialized from dae (COLLADA) file. 15 | ## Contains material parameters for shading. 16 | name*: string 17 | emission*: array[4, float32] 18 | ambient*: array[4, float32] 19 | diffuse*: array[4, float32] 20 | specular*: array[4, float32] 21 | shininess*: float32 22 | reflective*: array[4, float32] 23 | reflectivity*: float32 24 | transparent*: array[4, float32] 25 | normalmap*: array[4, float32] 26 | transparency*: float32 27 | emissionTextureName*: string 28 | ambientTextureName*: string 29 | diffuseTextureName*: string 30 | specularTextureName*: string 31 | reflectiveTextureName*: string 32 | transparentTextureName*: string 33 | normalmapTextureName*: string 34 | 35 | ColladaImage* = object 36 | ## Reference to Image file 37 | name*: string 38 | location*: string 39 | 40 | ColladaFaceAccessor* = object 41 | vertexOfset*: int 42 | normalOfset*: int 43 | texcoordOfset*: int 44 | 45 | ColladaGeometry* = ref object 46 | name*: string 47 | materialName*: string 48 | vertices*: seq[float32] 49 | texcoords*: seq[float32] 50 | normals*: seq[float32] 51 | triangles*: seq[int] 52 | faceAccessor*: ColladaFaceAccessor 53 | 54 | ColladaNode* = ref object 55 | name*: string 56 | matrix*: seq[float32] 57 | translation*: string 58 | rotationX*: string 59 | rotationY*: string 60 | rotationZ*: string 61 | scale*: string 62 | geometry*: string 63 | material*: string 64 | children*: seq[ColladaNode] 65 | kind*: NodeKind 66 | 67 | ChannelKind* {.pure.} = enum 68 | ## Kind of channel interpretation (at least like Maya names it) 69 | Matrix = "matrix" 70 | Visibility = "visibility" 71 | 72 | NodeKind* {.pure.} = enum 73 | ## Kind of node 74 | Node 75 | Joint 76 | 77 | ColladaChannel* = ref object 78 | ## Collada Animation Channel 79 | source*: string 80 | target*: string 81 | kind*: ChannelKind 82 | 83 | SourceKind* {.pure.} = enum 84 | ## Kind of source data stored in COLLADA file 85 | IDREF 86 | Name 87 | Bool 88 | Float 89 | Int 90 | 91 | ColladaSource* = ref object 92 | ## Source data stored in COLLADA file 93 | id*: string 94 | case kind*: SourceKind 95 | of SourceKind.IDREF: 96 | dataIDREF*: seq[string] 97 | of SourceKind.Name: 98 | dataName*: seq[string] 99 | of SourceKind.Bool: 100 | dataBool*: seq[bool] 101 | of SourceKind.Float: 102 | dataFloat*: seq[float32] 103 | of SourceKind.Int: 104 | dataInt*: seq[int32] 105 | paramType*: string 106 | 107 | ColladaInput = ref object 108 | ## Collada Input Definition 109 | semantic*: string 110 | source*: string 111 | 112 | ColladaSampler* = ref object 113 | ## Collada Animation Sampler 114 | id*: string 115 | input*: ColladaInput 116 | output*: ColladaInput 117 | inTangent*: ColladaInput 118 | outTangent*: ColladaInput 119 | interpolation*: ColladaInput 120 | 121 | ColladaAnimation* = ref object 122 | ## Collada Animation Descriptor 123 | id*: string 124 | children*: seq[ColladaAnimation] 125 | sources* : seq[ColladaSource] 126 | sampler* : ColladaSampler 127 | channel* : ColladaChannel 128 | 129 | ColladaSkinController* = ref object 130 | id*: string 131 | source*: string 132 | sources*: seq[ColladaSource] 133 | bindShapeMatrix*: seq[float32] 134 | weightsPerVertex*: int 135 | influences*: seq[int16] 136 | 137 | ColladaScene* = ref object 138 | path*: string 139 | rootNode*: ColladaNode 140 | childNodesGeometry*: seq[ColladaGeometry] 141 | childNodesMaterial*: seq[ColladaMaterial] 142 | childNodesImages*: seq[ColladaImage] 143 | animations*: seq[ColladaAnimation] 144 | skinControllers*: seq[ColladaSkinController] 145 | 146 | InterpolationKind* {.pure.} = enum 147 | Linear = "LINEAR" 148 | Bezier = "BEZIER" 149 | Cardinal = "CARDINAL" 150 | Hermite = "HERMITE" 151 | Bspline = "BSPLINE" 152 | Step = "STEP" 153 | 154 | const 155 | ## Input semantics kinds. These are made constants, not enums because 'semantics' 156 | ## value set is open, and may contain more values than those predefined 157 | ## in COLLADA 1.4 Standard. 158 | isInput* = "INPUT" 159 | isInterpolation* = "INTERPOLATION" 160 | isInTangent* = "IN_TANGENT" 161 | isOutTangent* = "OUT_TANGENT" 162 | isOutput* = "OUTPUT" 163 | 164 | const 165 | ## Source param type defines how source data must be interpreted 166 | ptName* = "name" 167 | ptFloat* = "float" 168 | ptFloat4x4* = "float4x4" 169 | 170 | const 171 | ## Collada XML Tag names according to COLLADA 1.4 Standard. 172 | csNone = "" 173 | csAsset = "asset" 174 | csLibraryImages = "library_images" 175 | csLibraryMaterial = "library_materials" 176 | csLibraryEffect = "library_effects" 177 | csLibraryGeometries = "library_geometries" 178 | csLibraryVisualScenes = "library_visual_scenes" 179 | csLibraryControllers = "library_controllers" 180 | csMaterial = "material" 181 | csID = "id" 182 | csName = "name" 183 | csEffect = "effect" 184 | csColor = "color" 185 | csFloat = "float" 186 | csTexture = "texture" 187 | csImage = "image" 188 | csInitFrom = "init_from" 189 | csEmission = "emission" 190 | csAmbient = "ambient" 191 | csDiffuse = "diffuse" 192 | csSpecular = "specular" 193 | csShininess = "shininess" 194 | csReflective = "reflective" 195 | csReflectivity = "reflectivity" 196 | csTransparent = "transparent" 197 | csTransparency = "transparency" 198 | csNormalmap = "normalmap" 199 | csGeometry = "geometry" 200 | csSource = "source" 201 | csFloatArray = "float_array" 202 | csNameArray = "Name_array" 203 | csAccessor = "accessor" 204 | csTriangles = "triangles" 205 | csSemantic = "semantic" 206 | csOffset = "offset" 207 | csInput = "input" 208 | csMesh = "mesh" 209 | csVertices = "vertices" 210 | csP = "p" 211 | csVisualScene = "visual_scene" 212 | csScene = "scene" 213 | csNode = "node" 214 | csType = "type" 215 | csMatrix = "matrix" 216 | csTranslate = "translate" 217 | csRotate = "rotate" 218 | csRotateZ = "rotateZ" 219 | csRotateY = "rotateY" 220 | csRotateX = "rotateX" 221 | csScale = "scale" 222 | csInstanceGeometry = "instance_geometry" 223 | csInstanceMaterial = "instance_material" 224 | csInstanceVisualScene = "instance_visual_scene" 225 | csVisibility = "visibility" 226 | csLibraryAnimations = "library_animations" 227 | csAnimation = "animation" 228 | csChannel = "channel" 229 | csSampler = "sampler" 230 | csExtra = "extra" 231 | csParam = "param" 232 | csController = "controller" 233 | csVertexWeights = "vertex_weights" 234 | csBindShapeMatrix = "bind_shape_matrix" 235 | 236 | proc isComplex*(anim: ColladaAnimation): bool = 237 | ## Checks if animation is a complex animation (has subanimations) or not 238 | return anim.children.len > 0 239 | 240 | proc parseArray4(source: string): array[0 .. 3, float32] = 241 | var i = 0 242 | for it in split(source): 243 | if it.len > 0: 244 | result[i] = parseFloat(it) 245 | inc(i) 246 | 247 | proc newColladaGeometry(): ColladaGeometry = 248 | result.new() 249 | result.vertices = newSeq[float32]() 250 | result.texcoords = newSeq[float32]() 251 | result.normals = newSeq[float32]() 252 | result.triangles = newSeq[int]() 253 | 254 | proc newColladaSource(kind: SourceKind): ColladaSource = 255 | ## Collada Source constructor 256 | result.new 257 | result.kind = kind 258 | case kind 259 | of SourceKind.IDREF: 260 | result.dataIDREF = @[] 261 | of SourceKind.Name: 262 | result.dataName = @[] 263 | of SourceKind.Bool: 264 | result.dataBool = @[] 265 | of SourceKind.Float: 266 | result.dataFloat = @[] 267 | of SourceKind.Int: 268 | result.dataInt = @[] 269 | 270 | proc newColladaChannel(): ColladaChannel = 271 | ## Create empty animation channel object 272 | result.new 273 | result.source = "" 274 | result.target = "" 275 | 276 | proc newColladaSampler(): ColladaSampler = 277 | ## Create empty animation sampler object 278 | result.new 279 | result.id = "" 280 | 281 | proc `$`*(c: ColladaInput): string = 282 | return " * $# source: ...$#)" % [c.semantic, c.source[^20..^1]] 283 | 284 | proc `$`*(s: ColladaSampler): string = 285 | ## Return text representation of the sampler 286 | result = "Sampler (id: $#):\n" % [s.id] 287 | if not isNil(s.input): result &= $s.input & "\n" 288 | else: result &= " * nil\n" 289 | if not isNil(s.output): result &= $s.output & "\n" 290 | else: result &= " * nil\n" 291 | if not isNil(s.inTangent): result &= $s.inTangent & "\n" 292 | else: result &= " * nil\n" 293 | if not isNil(s.outTangent): result &= $s.outTangent & "\n" 294 | else: result &= " * nil\n" 295 | if not isNil(s.interpolation): result &= $s.interpolation 296 | else: result &= " * nil" 297 | 298 | proc newColladaAnimation(): ColladaAnimation = 299 | ## Create empty animation object 300 | result.new 301 | result.id = "" 302 | result.children = @[] 303 | result.sources = @[] 304 | 305 | proc sourceById*(a: ColladaAnimation, n: string): ColladaSource = 306 | ## Returns source bound to animation by its id 307 | result = nil 308 | for s in a.sources: 309 | if s.id == n: 310 | return s 311 | 312 | proc newColladaNode(): ColladaNode = 313 | result.new() 314 | result.children = @[] 315 | 316 | proc newColladaScene(): ColladaScene = 317 | result.new() 318 | result.path = "" 319 | result.rootNode = newColladaNode() 320 | result.childNodesGeometry = @[] 321 | result.childNodesMaterial = @[] 322 | result.childNodesImages = @[] 323 | result.animations = @[] 324 | 325 | proc parseImages(x: var XmlParser, images: var seq[ColladaImage]) = # collect textures location 326 | while true: 327 | x.next() 328 | case x.kind 329 | of xmlElementOpen: 330 | case x.elementName: 331 | of csImage: 332 | x.next() 333 | # img id = x.attrValue()[0 .. ^1] 334 | x.next() 335 | var img: ColladaImage 336 | img.name = x.attrValue()[0 .. ^1] 337 | while true: 338 | x.next() 339 | case x.kind 340 | of xmlElementStart: 341 | case x.elementName: 342 | of csInitFrom: 343 | x.next() 344 | img.location = x.charData()[0 .. ^1] 345 | else: discard 346 | of xmlElementEnd: 347 | case x.elementName: 348 | of csImage: break 349 | of csLibraryImages: break 350 | else: discard 351 | of xmlEof: break 352 | else: discard 353 | images.add(img) 354 | else: discard 355 | of xmlElementEnd: 356 | case x.elementName: 357 | of csLibraryImages: break 358 | else: discard 359 | of xmlEof: break 360 | else: discard 361 | 362 | proc parseMaterialElement(x: var XmlParser, matVector: var array[0 .. 3, float32], matTextureName: var string) = 363 | while true: 364 | x.next() 365 | case x.kind: 366 | of xmlElementOpen: 367 | case x.elementName: 368 | of csTexture: 369 | x.next() 370 | matTextureName = x.attrValue()[0 .. ^1] 371 | break 372 | of csColor: 373 | while x.kind != xmlCharData: 374 | x.next() 375 | matVector = parseArray4(x.charData) 376 | break 377 | else: discard 378 | of xmlElementClose: 379 | break 380 | of xmlEof: break 381 | else: discard 382 | 383 | proc parseMaterialEffect(x: var XmlParser, materials: var seq[ColladaMaterial]) = # material settings 384 | while true: 385 | x.next() 386 | case x.kind 387 | of xmlElementOpen: 388 | case x.elementName: 389 | of csEffect: 390 | var mat: ColladaMaterial 391 | x.next() 392 | mat.name = x.attrValue()[0 .. ^1] 393 | while true: 394 | x.next() 395 | case x.kind 396 | of xmlElementStart: 397 | case x.elementName: 398 | of csEmission: 399 | x.parseMaterialElement(mat.ambient, mat.emissionTextureName) 400 | of csAmbient: 401 | x.parseMaterialElement(mat.ambient, mat.ambientTextureName) 402 | of csDiffuse: 403 | x.parseMaterialElement(mat.diffuse, mat.diffuseTextureName) 404 | of csSpecular: 405 | x.parseMaterialElement(mat.specular, mat.specularTextureName) 406 | of csShininess: 407 | while x.kind != xmlCharData: 408 | x.next() 409 | mat.shininess = parseFloat(x.charData) 410 | of csReflective: 411 | x.parseMaterialElement(mat.reflective, mat.reflectiveTextureName) 412 | of csReflectivity: 413 | while x.kind != xmlCharData: 414 | x.next() 415 | mat.reflectivity = parseFloat(x.charData) 416 | of csTransparent: 417 | x.parseMaterialElement(mat.transparent, mat.transparentTextureName) 418 | of csTransparency: 419 | while x.kind != xmlCharData: 420 | x.next() 421 | mat.transparency = parseFloat(x.charData) 422 | of csNormalmap: 423 | x.parseMaterialElement(mat.normalmap, mat.normalmapTextureName) 424 | else: discard 425 | of xmlElementEnd: 426 | case x.elementName: 427 | of csEffect: break 428 | else: discard 429 | of xmlEof: break 430 | else: discard 431 | materials.add(mat) 432 | else: discard 433 | 434 | of xmlElementEnd: 435 | case x.elementName: 436 | of csLibraryEffect: break 437 | else: discard 438 | of xmlEof: break 439 | else: discard 440 | 441 | proc parseMesh(x: var XmlParser, geomObject: ColladaGeometry) = 442 | var vertexSemantics = newSeq[string]() 443 | 444 | while true: 445 | x.next() 446 | case x.kind 447 | of xmlElementOpen: 448 | case x.elementName: 449 | of csFloatArray: 450 | x.next() 451 | var arrayID = x.attrValue[0 .. ^1] 452 | while x.kind != xmlCharData: 453 | x.next() 454 | if arrayID.contains("POSITION") or arrayID.contains("Position") or arrayID.contains("position"): 455 | for it in split(x.charData()): 456 | if it.len > 0: 457 | geomObject.vertices.add(parseFloat(it)) 458 | elif arrayID.contains("NORMAL") or arrayID.contains("Normal") or arrayID.contains("normal"): 459 | for it in split(x.charData()): 460 | if it.len > 0: 461 | geomObject.normals.add(parseFloat(it)) 462 | elif arrayID.contains("UV") or arrayID.contains("Uv") or arrayID.contains("uv"): 463 | for it in split(x.charData()): 464 | if it.len > 0: 465 | geomObject.texcoords.add(parseFloat(it)) 466 | else: 467 | echo("no vertex data in node") 468 | of csVertices: 469 | while true: 470 | x.next() 471 | case x.kind 472 | of xmlElementOpen: 473 | case x.elementName: 474 | of csInput: 475 | x.next() 476 | vertexSemantics.add(x.attrValue[0 .. ^1]) 477 | else: discard 478 | of xmlElementEnd: 479 | case x.elementName: 480 | of csVertices: break 481 | else: discard 482 | of xmlEof: break 483 | else: discard 484 | of csTriangles: 485 | x.next() 486 | x.next() 487 | geomObject.materialName = x.attrValue[0 .. ^1] 488 | while true: 489 | x.next() 490 | case x.kind 491 | of xmlElementOpen: 492 | case x.elementName: 493 | of csInput: 494 | x.next() 495 | var semantic = x.attrValue[0 .. ^1] 496 | x.next() 497 | var offset = parseInt(x.attrValue) 498 | if semantic == "VERTEX": 499 | for it in vertexSemantics: 500 | if it == "POSITION": 501 | geomObject.faceAccessor.vertexOfset = offset 502 | if it == "NORMAL": 503 | geomObject.faceAccessor.normalOfset = offset 504 | if it == "TEXCOORD": 505 | geomObject.faceAccessor.texcoordOfset = offset 506 | elif semantic == "NORMAL": 507 | geomObject.faceAccessor.normalOfset = offset 508 | elif semantic == "TEXCOORD": 509 | geomObject.faceAccessor.texcoordOfset = offset 510 | else: 511 | echo("corrupt face data") 512 | else: discard 513 | of xmlElementStart: 514 | case x.elementName: 515 | of csP: 516 | x.next() 517 | for it in split(x.charData()): 518 | geomObject.triangles.add(parseInt(it)) 519 | else: discard 520 | of xmlElementEnd: 521 | case x.elementName: 522 | of csTriangles: break 523 | else: discard 524 | of xmlEof: break 525 | else: discard 526 | else: discard 527 | of xmlElementEnd: 528 | case x.elementName: 529 | of csMesh: break 530 | else: discard 531 | of xmlEof: break 532 | else: discard 533 | 534 | proc parseGeometry(x: var XmlParser, geom: var seq[ColladaGeometry]) = 535 | while true: 536 | x.next() 537 | case x.kind 538 | of xmlElementOpen: 539 | case x.elementName: 540 | of csGeometry: 541 | let geomObject = newColladaGeometry() 542 | geom.add(geomObject) 543 | x.next() 544 | geomObject.name = x.attrValue[0 .. ^1] 545 | while true: 546 | x.next() 547 | case x.kind 548 | of xmlElementStart: 549 | case x.elementName: 550 | of csMesh: 551 | x.parseMesh(geomObject) 552 | else: discard 553 | of xmlElementEnd: 554 | case x.elementName: 555 | of csGeometry: break 556 | else: discard 557 | of xmlEof: break 558 | else: discard 559 | else: discard 560 | of xmlElementEnd: 561 | case x.elementName: 562 | of csLibraryGeometries: break 563 | else: discard 564 | of xmlEof: break 565 | else: discard 566 | 567 | proc parseNode(x: var XmlParser): ColladaNode = 568 | result = newColladaNode() 569 | 570 | while true: 571 | x.next() 572 | case x.kind 573 | of xmlAttribute: 574 | case x.attrKey: 575 | of csName: 576 | result.name = x.attrValue()[0 .. ^1] 577 | of csType: 578 | case x.attrValue(): 579 | of "JOINT": result.kind = NodeKind.Joint 580 | of "NODE": result.kind = NodeKind.Node 581 | else: discard 582 | else: discard 583 | of xmlElementOpen: 584 | case x.elementName: 585 | of csMatrix: 586 | while true: 587 | x.next() 588 | case x.kind 589 | of xmlElementClose: break 590 | of xmlEof: break 591 | else: discard 592 | x.next() 593 | result.matrix = @[] 594 | for part in x.charData().split(): 595 | if part.len > 0: 596 | result.matrix.add(parseFloat(part)) 597 | of csTranslate: 598 | while x.kind != xmlCharData: 599 | x.next() 600 | result.translation = x.charData()[0 .. ^1] 601 | of csRotate: 602 | x.next() 603 | if x.attrValue == csRotateZ: 604 | while x.kind != xmlCharData: 605 | x.next() 606 | result.rotationZ = x.charData()[0 .. ^1] 607 | elif x.attrValue == csRotateY: 608 | while x.kind != xmlCharData: 609 | x.next() 610 | result.rotationY = x.charData()[0 .. ^1] 611 | elif x.attrValue == csRotateX: 612 | while x.kind != xmlCharData: 613 | x.next() 614 | result.rotationX = x.charData()[0 .. ^1] 615 | of csScale: 616 | while x.kind != xmlCharData: 617 | x.next() 618 | result.scale = x.charData()[0 .. ^1] 619 | of csInstanceMaterial: 620 | x.next() 621 | result.material = x.attrValue()[0 .. ^1] 622 | of csInstanceGeometry: 623 | x.next() 624 | result.geometry = x.attrValue()[0 .. ^1] 625 | of csNode: 626 | result.children.add(parseNode(x)) 627 | else: discard 628 | of xmlElementEnd: 629 | case x.elementName: 630 | of csNode: break 631 | else: discard 632 | of xmlEof: break 633 | else: discard 634 | 635 | proc parseChannel(x: var XmlParser): ColladaChannel = 636 | ## Parse tag 637 | result.new 638 | result.source = "" 639 | result.target = "" 640 | 641 | while true: 642 | case x.kind 643 | of xmlAttribute: 644 | case x.attrKey 645 | of "source": 646 | result.source = x.attrValue[1..^1] 647 | of "target": 648 | result.target = x.attrValue.split("/")[0] 649 | result.kind = case x.attrValue.split("/")[^1] 650 | of "matrix": ChannelKind.Matrix 651 | of "visibility": ChannelKind.Visibility 652 | else: ChannelKind.Matrix 653 | else: 654 | discard 655 | of xmlElementEnd: 656 | break 657 | else: 658 | discard 659 | x.next() 660 | 661 | proc parseInput(x: var XmlParser): ColladaInput = 662 | ## Parse tag 663 | result.new 664 | result.semantic = "" 665 | result.source = "" 666 | 667 | while true: 668 | case x.kind 669 | of xmlAttribute: 670 | case x.attrKey 671 | of "semantic": 672 | result.semantic = x.attrValue 673 | of "source": 674 | result.source = x.attrValue[1..^1] 675 | else: 676 | discard 677 | of xmlElementClose: 678 | return 679 | else: 680 | discard 681 | x.next() 682 | 683 | proc parseSampler(x: var XmlParser): ColladaSampler = 684 | ## Parse tag 685 | result = newColladaSampler() 686 | 687 | while true: 688 | case x.kind 689 | of xmlElementOpen: 690 | case x.elementName 691 | of csInput: 692 | let parsedInput = x.parseInput() 693 | case parsedInput.semantic 694 | of isInput: 695 | result.input = parsedInput 696 | of isOutput: 697 | result.output = parsedInput 698 | of isInTangent: 699 | result.inTangent = parsedInput 700 | of isOutTangent: 701 | result.outTangent = parsedInput 702 | of isInterpolation: 703 | result.interpolation = parsedInput 704 | else: 705 | discard 706 | else: 707 | discard 708 | of xmlAttribute: 709 | if x.attrKey == "id": 710 | result.id = x.attrValue 711 | of xmlElementEnd: 712 | if x.elementName == csSampler: 713 | return 714 | else: 715 | discard 716 | x.next() 717 | 718 | proc parseSource(x: var XmlParser): ColladaSource = 719 | ## Parse tag 720 | 721 | var 722 | localContext = csSource 723 | counter = 0 724 | resultId = "" 725 | 726 | while true: 727 | x.next() 728 | case x.kind 729 | of xmlAttribute: 730 | if x.attrKey == "id": 731 | if localContext == csSource: 732 | resultId = x.attrValue 733 | elif localContext == csFloatArray: 734 | result = ColladaSource(kind: SourceKind.Float) 735 | elif localContext == csNameArray: 736 | result = ColladaSource(kind: SourceKind.Name) 737 | elif x.attrKey == "count": 738 | counter = x.attrValue.parseInt() 739 | elif x.attrKey == "type": 740 | if localContext == csParam: 741 | result.paramType = x.attrValue 742 | of xmlCharData: 743 | for piece in x.charData.split({' ', '\L', '\r'}): 744 | if piece.len > 0: 745 | assert(not result.isNil) # TODO: More elaborate handling might be required 746 | if result.kind == SourceKind.Float: 747 | result.dataFloat.add(piece.parseFloat()) 748 | else: 749 | result.dataName.add(piece) 750 | of xmlElementOpen: 751 | if x.elementName == csFloatArray: 752 | localContext = csFloatArray 753 | elif x.elementName == csNameArray: 754 | localContext = csNameArray 755 | elif x.elementName == csParam: 756 | localContext = csParam 757 | of xmlElementEnd: 758 | if x.elementName == csSource: 759 | break 760 | else: 761 | discard 762 | 763 | assert(not result.isNil) # TODO: More elaborate handling might be required 764 | result.id = resultId 765 | 766 | proc parseAnimation(x: var XmlParser): ColladaAnimation = 767 | ## Parse tag 768 | result = newColladaAnimation() 769 | 770 | while true: 771 | x.next() 772 | case x.kind 773 | of xmlAttribute: 774 | case x.attrKey 775 | of "id": 776 | result.id = x.attrValue 777 | else: 778 | discard 779 | of xmlElementStart: 780 | if x.elementName == csAnimation: 781 | result.children.add(x.parseAnimation()) 782 | of xmlElementOpen: 783 | case x.elementName 784 | of csAnimation: 785 | discard 786 | of csChannel: 787 | result.channel = x.parseChannel() 788 | of csSource: 789 | result.sources.add(x.parseSource()) 790 | of csSampler: 791 | result.sampler = x.parseSampler() 792 | else: 793 | discard 794 | of xmlElementClose: 795 | continue 796 | of xmlElementEnd: 797 | case x.elementName 798 | of csAnimation: 799 | return result 800 | else: 801 | discard 802 | else: 803 | discard 804 | 805 | proc parseAnimations(x: var XmlParser, cs: var ColladaScene) = 806 | ## Parse tag 807 | while true: 808 | x.next() 809 | case x.kind 810 | of xmlElementOpen: 811 | cs.animations.add(x.parseAnimation()) 812 | of xmlElementEnd: 813 | case x.elementName 814 | of csLibraryAnimations: 815 | break 816 | else: 817 | discard 818 | else: 819 | discard 820 | 821 | proc parseScene(x: var XmlParser, cs: var ColladaScene) = 822 | ## Parse entire scene stored in COLLADA file 823 | while true: 824 | x.next() 825 | case x.kind 826 | of xmlElementOpen: 827 | case x.elementName: 828 | of csVisualScene: 829 | x.next() 830 | # sceneNodeID = x.attrValue 831 | x.next() 832 | cs.rootNode.name = x.attrValue()[0 .. ^1] 833 | of csNode: 834 | cs.rootNode.children.add(parseNode(x)) 835 | else: 836 | discard 837 | of xmlEof: 838 | break 839 | else: 840 | discard 841 | 842 | proc skipUntilClose(x: var XmlParser) = 843 | var opens = 0 844 | while true: 845 | x.next() 846 | case x.kind 847 | of xmlElementOpen: inc opens 848 | of xmlElementClose: 849 | if opens == 0: break 850 | dec opens 851 | else: discard 852 | 853 | proc parseVertexWeights(x: var XmlParser, sc: ColladaSkinController) = 854 | var vcount = newSeq[int]() 855 | sc.influences = @[] 856 | while true: 857 | x.next() 858 | case x.kind 859 | of xmlElementStart: 860 | case x.elementName 861 | of "vcount": 862 | while x.kind != xmlCharData: 863 | x.next() 864 | for part in x.charData().split(): 865 | let c = parseInt(part) 866 | sc.weightsPerVertex = max(c, sc.weightsPerVertex) 867 | vcount.add(c) 868 | sc.influences = newSeq[int16](sc.weightsPerVertex * vcount.len * 2) 869 | for i in 0 ..< sc.influences.len: 870 | sc.influences[i] = -1 871 | of "v": 872 | while x.kind != xmlCharData: 873 | x.next() 874 | assert(sc.weightsPerVertex != 0) 875 | var curVertex = 0 876 | var curBone = 0 877 | var isBone = 0 878 | for part in x.charData().split(): 879 | sc.influences[(curVertex * sc.weightsPerVertex + curBone) * 2 + isBone] = int16(parseInt(part)) 880 | if isBone == 1: 881 | isBone = 0 882 | inc curBone 883 | if curBone == vcount[curVertex]: 884 | curBone = 0 885 | inc curVertex 886 | else: 887 | isBone = 1 888 | else: 889 | discard 890 | of xmlElementEnd: 891 | case x.elementName 892 | of csVertexWeights: 893 | break 894 | else: 895 | discard 896 | else: 897 | discard 898 | 899 | proc parseSkinController(x: var XmlParser, cs: var ColladaScene): ColladaSkinController = 900 | result.new() 901 | result.sources = @[] 902 | while true: 903 | x.next() 904 | case x.kind 905 | of xmlAttribute: 906 | case x.attrKey 907 | of csId: result.id = x.attrValue[0 .. ^1] 908 | of csSource: result.source = x.attrValue[0 .. ^1] 909 | else: discard 910 | of xmlElementOpen: 911 | case x.elementName: 912 | of csSource: 913 | result.sources.add(parseSource(x)) 914 | of csVertexWeights: 915 | parseVertexWeights(x, result) 916 | else: 917 | discard 918 | of xmlElementStart: 919 | case x.elementName 920 | of csBindShapeMatrix: 921 | while x.kind != xmlCharData: 922 | x.next() 923 | result.bindShapeMatrix = @[] 924 | for part in x.charData().split(): 925 | if part.len > 0: 926 | result.bindShapeMatrix.add(parseFloat(part)) 927 | else: 928 | discard 929 | of xmlElementEnd: 930 | if x.elementName == csController: break 931 | else: discard 932 | 933 | proc parseControllers(x: var XmlParser, cs: var ColladaScene) = 934 | ## Parse tag 935 | while true: 936 | x.next() 937 | case x.kind 938 | of xmlElementOpen: 939 | case x.elementName: 940 | of csController: 941 | cs.skinControllers.add(parseSkinController(x, cs)) 942 | else: 943 | discard 944 | else: 945 | break 946 | 947 | proc load*(loader: ColladaLoader, s: Stream): ColladaScene = 948 | ## Load Entire Scene from COLLADA file 949 | result = newColladaScene() 950 | 951 | var x: XmlParser 952 | x.open(s, "") 953 | x.next() 954 | 955 | while true: 956 | case x.kind 957 | of xmlElementStart: 958 | case x.elementName 959 | of csLibraryImages: 960 | x.parseImages(result.childNodesImages) 961 | of csLibraryEffect: 962 | x.parseMaterialEffect(result.childNodesMaterial) 963 | of csLibraryGeometries: 964 | x.parseGeometry(result.childNodesGeometry) 965 | of csLibraryVisualScenes: 966 | x.parseScene(result) 967 | of csLibraryAnimations: 968 | x.parseAnimations(result) 969 | of csLibraryControllers: 970 | x.parseControllers(result) 971 | else: 972 | x.next() 973 | of xmlEof: 974 | break 975 | else: 976 | x.next() 977 | 978 | proc `$`*(c: ColladaChannel): string = 979 | ## Return text representation of the animation channel 980 | if not isNil(c): 981 | return "Channel (source: ...$#, target: .../$#, kind: $#)" % [c.source[^20..^1], c.target, $c.kind] 982 | else: 983 | return "Channel NIL" 984 | 985 | proc `$`*(c: ColladaSource): string = 986 | ## Return text representation of the animation source 987 | result = "Source (id: ...$#, kind: $#, paramType: $#, data: " % [($c.id)[^20..^1], $c.kind, $c.paramType] 988 | 989 | case c.kind 990 | of SourceKind.IDREF: 991 | result &= "$#...] ($#)" % [($c.dataIDREF)[0..40], $c.dataIDREF.len] 992 | of SourceKind.Name: 993 | result &= "$#...] ($#)" % [($c.dataName)[0..40], $c.dataName.len] 994 | of SourceKind.Bool: 995 | result &= "$#...] ($#)" % [($c.dataBool)[0..40], $c.dataBool.len] 996 | of SourceKind.Float: 997 | result &= "$#...] ($#)" % [($c.dataFloat)[0..40], $c.dataFloat.len] 998 | of SourceKind.Int: 999 | result &= "$#...] ($#)" % [($c.dataInt)[0..40], $c.dataInt.len] 1000 | 1001 | proc `$`*(anim: ColladaAnimation): string = 1002 | ## Text representation of animation info 1003 | result = " * Animation: $#\n" % [anim.id] 1004 | if not isNil(anim.channel): 1005 | result &= " * $#\n" % [$anim.channel] 1006 | if not isNil(anim.sampler): 1007 | result &= " * $#\n" % [$anim.sampler] 1008 | for source in anim.sources: 1009 | result &= " * $#\n" % [$source] 1010 | if anim.children.len > 0: 1011 | result &= " | Animation children:\n" 1012 | for a in anim.children: 1013 | result &= $a 1014 | if anim.children.len > 0: 1015 | result &= " ---------------------" 1016 | 1017 | proc `$`*(scene: ColladaScene): string = 1018 | ## Perform text representaiton of the scene 1019 | result = "COLLADA Scene: '" & scene.path & "'\n" 1020 | result &= " * Animations [$#]: \n" % [$scene.animations.len] 1021 | for anim in scene.animations: 1022 | result &= $anim 1023 | 1024 | proc boneAndWeightForVertex*(sc: ColladaSkinController, vertexIndex, boneIndex: int): tuple[bone: string, weight: float32] = 1025 | # Not recommended for performance reasons 1026 | for s in sc.sources: 1027 | if s.id.endsWith("-Weights"): 1028 | let infl = sc.influences[(vertexIndex * sc.weightsPerVertex + boneIndex) * 2 + 1] 1029 | if infl == -1: 1030 | result.weight = 0.0 1031 | else: 1032 | result.weight = s.dataFloat[infl] 1033 | 1034 | elif s.kind == SourceKind.Name: 1035 | let infl = sc.influences[(vertexIndex * sc.weightsPerVertex + boneIndex) * 2 + 0] 1036 | if infl == -1: 1037 | result.bone = "" 1038 | else: 1039 | result.bone = s.dataName[sc.influences[(vertexIndex * sc.weightsPerVertex + boneIndex) * 2 + 0]] 1040 | 1041 | proc boneInvMatrix*(sc: ColladaSkinController, boneName: string): seq[float32] = 1042 | # var matrixes = newSeq[array[16, float32]]() 1043 | # var names = newSeq[string]() 1044 | var index = -1 1045 | 1046 | for s in sc.sources: 1047 | if s.id.endsWith("-Joints"): 1048 | for i in 0 ..< s.dataName.len: 1049 | if s.dataName[i] == boneName: 1050 | index = i 1051 | 1052 | if index == -1: 1053 | return @[] 1054 | 1055 | for s in sc.sources: 1056 | if s.id.endsWith("-Matrices"): 1057 | var matData = newSeq[float32](16) 1058 | for i in 0..15: 1059 | matData[i] = s.dataFloat[16 * index + i] 1060 | 1061 | return matData 1062 | 1063 | 1064 | when isMainModule and not defined(js): 1065 | let 1066 | f = open("balloon_animation_test.dae") 1067 | fs = newFileStream(f) 1068 | loader = ColladaLoader.new 1069 | scene = loader.load(fs) 1070 | 1071 | echo scene 1072 | #[ 1073 | echo boneAndWeightForVertex(scene.skinControllers[0], 0, 0) 1074 | echo boneAndWeightForVertex(scene.skinControllers[0], 1, 0) 1075 | echo boneAndWeightForVertex(scene.skinControllers[0], 8, 0) 1076 | ]# 1077 | -------------------------------------------------------------------------------- /nimasset/fbx.nim: -------------------------------------------------------------------------------- 1 | import streams 2 | import pegs 3 | import times 4 | import strutils 5 | 6 | type 7 | ErrBadFBX* = ref object of Exception 8 | ## Bad-formed Autodesk FBX error 9 | 10 | ErrUnsupported* = ref object of Exception 11 | ## Something is unsupported here 12 | 13 | FBXKind* {.pure.} = enum 14 | Binary 15 | Ascii 16 | 17 | FBXLoader* = ref object 18 | ## Loads Autodesk FBX (*.fbx) format for 3D assets 19 | 20 | FBXGeometryNode* = ref object 21 | 22 | FBXScene* = ref object 23 | ## Scene imported from OGEX file format 24 | geometry: seq[FBXGeometryNode] 25 | 26 | proc newFBXScene*(): FBXScene = 27 | ## Empty Ogex Scene constructore 28 | result.new 29 | result.geometry = @[] 30 | 31 | proc load*(loader: FBXLoader, s: Stream, kind: FBXKind = FBXKind.Ascii): FBXScene = 32 | ## Load FBX-encoded data from stream 33 | if kind == FBXKind.Binary: 34 | raise new(ErrUnsupported) 35 | return newFBXScene() 36 | 37 | proc `$`(scene: FBXScene): string = 38 | ## Ogex string stringificator 39 | result = "FBX Scene {\n" 40 | result &= "}" 41 | 42 | when isMainModule and not defined(js): 43 | let 44 | f = open("teapot.fbx") 45 | fs = newFileStream(f) 46 | loader = FBXLoader.new 47 | scene = loader.load(fs) 48 | 49 | echo scene 50 | -------------------------------------------------------------------------------- /nimasset/obj.nim: -------------------------------------------------------------------------------- 1 | import streams 2 | import strutils 3 | 4 | import asset_types 5 | 6 | type 7 | ObjLoaderObj = object 8 | ObjLoader* = ref ObjLoaderObj # Loads WafeFront OBJ format for 3D assets 9 | 10 | # addVertex : expr = call 11 | # addTexture : expr = call 12 | # addIndex: expr = call 13 | # ret void / bool / exception 14 | template loadMeshData*(loader: ObjLoader, s: Stream, addVertex: untyped, addTexture: untyped, addNormal: untyped, addFace: untyped) = 15 | ## Loads mesh data from stream defined in streams module of 16 | ## standard library. 17 | var 18 | line: string = "" 19 | while s.readLine(line): 20 | # Parse line 21 | line = line.strip() 22 | let components = line.split() 23 | 24 | if components.len() == 0: 25 | continue 26 | elif components[0] == "#": # Comment 27 | continue 28 | elif components[0] == "v": # Vertex data 29 | addVertex(parseFloat(components[1]), parseFloat(components[2]), parseFloat(components[3])) 30 | elif components[0] == "vt": # Vertex Texture data 31 | addTexture(parseFloat(components[1]), parseFloat(components[2]), parseFloat(components[3])) 32 | elif components[0] == "vn": # Vertext Normals data 33 | addNormal(parseFloat(components[1]), parseFloat(components[2]), parseFloat(components[3])) 34 | elif components[0] == "f": 35 | let comnponentsCount = components[1].count("/") + 1 36 | if comnponentsCount == 1: # Only vertices in face data 37 | addFace(parseInt(components[1]), parseInt(components[2]), parseInt(components[3]), 0, 0, 0, 0, 0, 0) 38 | continue 39 | elif comnponentsCount >= 2: # Vertex, Normal and Texture data in face data 40 | let 41 | block_1 = components[1].split("/") 42 | block_2 = components[2].split("/") 43 | block_3 = components[3].split("/") 44 | vi_0 = parseInt(block_1[0]) 45 | vi_1 = parseInt(block_2[0]) 46 | vi_2 = parseInt(block_3[0]) 47 | ti_0 = parseInt(block_1[1]) 48 | ti_1 = parseInt(block_2[1]) 49 | ti_2 = parseInt(block_3[1]) 50 | var 51 | ni0 = 0 52 | ni1 = 0 53 | ni2 = 0 54 | 55 | if comnponentsCount >= 3: 56 | ni0 = parseInt(block_1[2]) 57 | ni1 = parseInt(block_2[2]) 58 | ni2 = parseInt(block_3[2]) 59 | addFace(vi_0, vi_1, vi_2, ti_0, ti_1, ti_2, ni0, ni1, ni2) 60 | 61 | template loadMeshData*(loader: ObjLoader, s: Stream, addVertex: untyped, addTexture: untyped, addFace: untyped) = 62 | template addNormal(x, y, z: float32) = discard 63 | loadMeshData(loader, s, addVertex, addTexture, addNormal, addFace) 64 | 65 | template loadMeshData*(loader: ObjLoader, data: pointer, addVertex: untyped, addTexture: untyped, addNormal: untyped, addFace: untyped) = 66 | ## Loads mesh data from given pointer as a source, and a size 67 | ## of data provided with pointer. 68 | loadMeshData(loader, newStringStream(`$`(cast[cstring](data))), addVertex, addTexture, addNormal, addFace) 69 | 70 | when not defined(js): 71 | template loadMeshData*(loader: ObjLoader, f: File, addVertex: untyped, addTexture: untyped, addNormal: untyped, addFace: untyped) = 72 | ## Loads mesh data from file 73 | loadMeshData(loader, newFileStream(f), addVertex, addTexture, addNormal, addFace) 74 | 75 | template loadMeshData*(loader: ObjLoader, data: string, addVertex: untyped, addTexture: untyped, addNormal: untyped, addFace: untyped) = 76 | ## Loads mesh data from string 77 | loadMeshData(loader, newStringStream(data), addVertex, addTexture, addNormal, addFace) 78 | 79 | 80 | when isMainModule and not defined(js): 81 | ## Testing OBjLoader: 82 | ## - Load Mesh Data on sample OBJ teapot model without textures 83 | let loader: ObjLoader = new(ObjLoaderObj) 84 | let f = open("teapot.obj") 85 | let fs = newFileStream(f) 86 | 87 | proc addVertex(x, y, z: float) = 88 | echo "Vertex: ", x, " ", y, " ", z 89 | 90 | proc addTexture(u, v, w: float) = 91 | echo "Texture: ", u, v, w 92 | 93 | proc addFace(vi0, vi1, vi2, ti0, ti1, ti2, ni0, ni1, ni2: int) = 94 | echo "Face: ", vi0, " ", vi1, " ", vi2, " ", ti0, " ", ti1, " ", ti2, " ", ni0, " ", ni1, " ", ni2 95 | 96 | loadMeshData(loader, fs, addVertex, addTexture, addFace) 97 | -------------------------------------------------------------------------------- /nimasset/ogex.nim: -------------------------------------------------------------------------------- 1 | import streams 2 | import pegs 3 | import times 4 | import strutils 5 | 6 | type 7 | ErrBadOgex* = ref object of Exception 8 | ## Bad-formed COLLADA error 9 | 10 | OgexLoader* = ref object 11 | ## Loads COLLADA (*.dae) format for 3D assets 12 | 13 | OgexGeometryNode* = ref object 14 | 15 | OgexScene* = ref object 16 | ## Scene imported from OGEX file format 17 | geometry: seq[OgexGeometryNode] 18 | 19 | proc newOgexScene*(): OgexScene = 20 | ## Empty Ogex Scene constructore 21 | result.new 22 | result.geometry = @[] 23 | 24 | proc load*(l: OgexLoader, s: Stream): OgexScene = 25 | ## Load OGEX-encoded data from stream 26 | 27 | proc `$`(scene: OgexScene): string = 28 | ## Ogex string stringificator 29 | result = "scene.ogex {\n" 30 | result &= "}" 31 | 32 | 33 | when isMainModule and not defined(js): 34 | let 35 | f = open("cube.ogex") 36 | fs = newFileStream(f) 37 | loader = OgexLoader.new 38 | scene = loader.load(fs) 39 | 40 | echo scene 41 | --------------------------------------------------------------------------------