I just wrote an obj loader for my game last week, obj files are text files that describes a 3d model. To stress test it I tried the common large minecraft model rungholt, this is a text file with over 9 million lines. Blender took 2 minutes to load the file and windows preview gave up. I load it in 1-2 seconds by inlining the strtofloat procedures like this: proc fromString*(m:ObjModels,s:string,materials:string)= # This has to be the format: # no textures or normals. # Blender export settings: # Z forward, Y up, # only theese checked: Apply modifiers, Write Materials, Triangulate Faces, Obj as obj groups # # mtllib watermill2.mtl # g watermill # v 0.285000 0.715365 -0.413016 # v 0.285000 0.774984 -0.052391 # usemtl woodDark # s 1 # f 1 2 3 # f 4 2 1 # Material string: # #[ # Blender v2.78 (sub 0) OBJ File: '' # www.blender.org mtllib test.mtl o sail_front_1 v -5.776396 31.717396 32.006035 v -5.776396 24.370396 32.006035 v 5.776396 24.370396 32.006035 v 5.776396 31.717396 32.006035 g sail_front_1_sail_front_1_textile usemtl textile s 1 f 1 2 3 f 3 4 1 f 5 1 6 f 7 1 4 f 4 8 7 # Blender MTL File: 'None' # Material Count: 7 newmtl Material Ns 96.078431 Ka 1.000000 1.000000 1.000000 Kd 0.640000 0.640000 0.640000 Ks 0.500000 0.500000 0.500000 Ke 0.000000 0.000000 0.000000 Ni 1.000000 d 1.000000 illum 2 newmtl _defaultMat Ns 96.078431 Ka 0.000000 0.000000 0.000000 Kd 1.000000 1.000000 1.000000 Ks 0.330000 0.330000 0.330000 Ke 0.000000 0.000000 0.000000 ]# m.faces.clear m.verts.clear m.material.clear m.materials.clear m.matnr.clear var currentmat = "" var lastmat:uint32 for i in materials.splitLines: if i.startswith("newmtl"): var mat:ObjMaterial mat.name = i.sfrom("newmtl ") m.material.add mat lastmat = m.material.len-1 elif i.startswith("Kd"): var c = i.split() let col = color(parsefloat(c[1]), parsefloat(c[2]), parsefloat(c[3]), 1.0) m.material[lastmat].color = col m.materials.add col.pack() m.matnr[m.material[lastmat].name] = int16(m.materials.len - 1) var onlyverts = false var first = true var currentMatnr:int16 = 0 var currentGroupnr:uint32 = 0 var i = 0 let slen = s.len-1 while i < slen: case s[i] of 'u': # usemtl if s.startswithat("usemtl",i): var matname = "" i += "usemtl ".len while s[i] notin {' ',chr(13),chr(10)}: matname.add s[i] inc i currentMatnr = m.matnr[matname] of 'g': # g group_0_4634441 while s[i] notin {' ',chr(13),chr(10)}: inc i of 'o': # o object_0_4634441 while s[i] notin {' ',chr(13),chr(10)}: inc i of 'v': # Only positions and colors, normals are calculated in the shader # Inlined for speed reasons, no string allocations etc, just scan directly # into float32 if s[i+1] == ' ': inc i inc i # var c = i.split({' '}) # Slow allocations here.. # m.verts.add parsefloat(c[1].strip()) # m.verts.add parsefloat(c[2].strip()) # m.verts.add parsefloat(c[3].strip()) # # Optimized version of the above below #v -5.776396 31.717396 32.006035 # This has to be the format, since we have control over this and know this # we can just use a simple string to float inline and do not have to # take care of all special cases etc, but more importantly we never have to # allocate any temps. var neg = false const zero = ord('0') const digits = {'0'..'9'} var number = 0f if (s[i] == '-'): neg = true inc i while s[i] in digits: number = ( number * 10f ) + ( ord(s[i]) - zero ) inc i if (s[i] == '.'): var f = 0f var n = 0f inc i while s[i] in digits: f = ( f * 10f ) + (ord(s[i]) - zero) inc i n = n + 1 number += f / pow(10f, n) if neg: number = -number m.verts.add number # X if s[i] == ' ': inc i neg = false number = 0f if (s[i] == '-'): neg = true inc i while s[i] in digits: number = ( number * 10f ) + ( ord(s[i]) - zero ) inc i if (s[i] == '.'): var f = 0f var n = 0f inc i while s[i] in digits: f = ( f * 10f ) + (ord(s[i]) - zero) inc i n = n + 1 number += f / pow(10f, n) if neg: number = -number m.verts.add number # Y if s[i] == ' ': inc i neg = false number = 0f if (s[i] == '-'): neg = true inc i while s[i] in digits: number = ( number * 10f ) + ( ord(s[i]) - zero ) inc i if (s[i] == '.'): var f = 0f var n = 0f inc i while s[i] in digits: f = ( f * 10f ) + (ord(s[i]) - zero) inc i n = n + 1 number += f / pow(10f, n) if neg: number = -number m.verts.add number # Z of 'f': #f 2 1 3 inc i var vno = 0'i32 while s[i] notin {'0'..'9'}: inc i while s[i] in {'0'..'9'}: vno = 10'i32 * vno + ord(s[i]).int32 - 48i32 inc i var vno2 = 0'i32 while s[i] notin {' '}: inc i while s[i] notin {'0'..'9'}: inc i while s[i] in {'0'..'9'}: vno2 = 10'i32 * vno2 + ord(s[i]).int32 - 48i32 inc i var vno3 = 0'i32 while s[i] notin {' '}: # Skip 2/2/1 format inc i while s[i] notin {'0'..'9'}: inc i while s[i] in {'0'..'9'}: vno3 = 10'i32 * vno3 + ord(s[i]).int32 - 48i32 inc i var f:ObjFace f.vert = (vno-1)*3 f.material = currentMatnr.int8 m.faces.add f.pack() f.vert = (vno2-1)*3 f.material = currentMatnr.int8 m.faces.add f.pack() f.vert = (vno3-1)*3 f.material = currentMatnr.int8 m.faces.add f.pack() else: discard # Skip rest of line while s[i] notin {chr(13),chr(10)} and i < slen: inc i while s[i] in {chr(13),chr(10)} and i < slen: inc i Run
StrToFloat is slow in every language, but If you know the source format you can just do an extremely fast inline version. 300k lines should never have to take more than a second to parse. (I'm doing 9 million in a second with the code above)