New version of obj2fvf.bbc

Discussions related to graphics (2D and 3D), animation and games programming
DDRM

New version of obj2fvf.bbc

Post by DDRM »

Some while ago, Richard gave us a program to convert OBJ format 3D files to the FVF format used by D3DLib (for BB4W) and the equivalent OGLLIB (for BBC-SDL). He subsequently found a bug, and has produced an updated version, included below.

You can see the original discussion in the old forum here:

http://www.bbcbasic.co.uk/oldforum/bbcb ... 1505507189

Best wishes,

D

Code: Select all

      REM Wavefront OBJ to FVF converter, Richard Russell, 22-Aug-2018
      HIMEM = PAGE + 20000000

      ObjFile$ = "trumpet.obj"
      ObjFile% = OPENIN(ObjFile$)
      IF ObjFile%=0 ERROR 100, "Can't open file " + ObjFile$

      IF RIGHT$(ObjFile$,4)=".obj" OR RIGHT$(ObjFile$,4)=".OBJ" THEN
        FVFfile$ = LEFT$(ObjFile$,LEN(ObjFile$)-4) + ".fvf"
      ELSE
        FVFfile$ = ObjFile$ + ".obj"
      ENDIF
      FVFfile% = OPENOUT(FVFfile$)
      IF FVFfile%=0 ERROR 100, "Can't create file " + FVFfile$
      PTR#FVFfile% = 8

      DIM c(100000,2), v(100000,2), t(100000,1), n(100000,2), p(2)

      nv% = 0    : REM Number of vertices
      nt% = 0    : REM Number of texture coordinates
      nn% = 0    : REM Number of normals
      nf% = 0    : REM Number of triangular faces
      vf% = &012 : REM Initial vertex format
      xsum = 0
      ysum = 0
      zsum = 0

      WHILE NOT EOF#ObjFile%
        a$ = GET$#ObjFile%
        type$ = LEFT$(a$,2)
        a$ = MID$(a$,3)
        CASE type$ OF
          WHEN "v ":
            v(nv%,0) = FNn(a$) : v(nv%,1) = FNn(a$) : v(nv%,2) = FNn(a$)
            c(nv%,0) = FNn(a$) : c(nv%,1) = FNn(a$) : c(nv%,2) = FNn(a$)
            IF c(nv%,1) <> 0 OR c(nv%,2) <> 0 vf% OR= &040 : REM Include diffuse colour
            nv% += 1
          WHEN "vt":
            t(nt%,0) = FNn(a$) : t(nt%,1) = FNn(a$)
            IF t(nv%,0) <> 0 OR t(nv%,1) <> 0 vf% OR= &100 : REM Include texture coords
            nt% += 1
          WHEN "vn":
            n(nn%,0) = FNn(a$) : n(nn%,1) = FNn(a$) : n(nn%,2) = FNn(a$)
            nn% += 1
          WHEN "f ":
            t1% = 0 : t2% = 0 : t3% = 0 : n1% = 0 : n2% = 0 : n3% = 0
            v1% = FNn(a$) : IF ASCa$=&2F t1% = FNn(a$) : IF ASCa$=&2F n1% = FNn(a$)
            v2% = FNn(a$) : IF ASCa$=&2F t2% = FNn(a$) : IF ASCa$=&2F n2% = FNn(a$)
            IF v1% > 0 v1% -= 1 ELSE v1% += nv%
            IF v2% > 0 v2% -= 1 ELSE v2% += nv%
            IF t1% > 0 t1% -= 1 ELSE t1% += nt%
            IF t2% > 0 t2% -= 1 ELSE t2% += nt%
            IF n1% > 0 n1% -= 1 ELSE n1% += nn%
            IF n2% > 0 n2% -= 1 ELSE n2% += nn%
            REPEAT
              v3% = FNn(a$) : IF ASCa$=&2F t3% = FNn(a$) : IF ASCa$=&2F n3% = FNn(a$)
              IF v3% = 0 EXIT REPEAT
        
              IF v3% > 0 v3% -= 1 ELSE v3% += nv%
              IF t3% > 0 t3% -= 1 ELSE t3% += nt%
              IF n3% > 0 n3% -= 1 ELSE n3% += nn%
        
              REM Vertex coordinates:
              x1 = v(v1%,0) : y1 = v(v1%,1) : z1 = v(v1%,2)
              x2 = v(v2%,0) : y2 = v(v2%,1) : z2 = v(v2%,2)
              x3 = v(v3%,0) : y3 = v(v3%,1) : z3 = v(v3%,2)
        
              REM Vertex colour:
              R1 = c(v1%,0) : G1 = c(v1%,1) : B1 = c(v1%,2)
              R2 = c(v2%,0) : G2 = c(v2%,1) : B2 = c(v2%,2)
              R3 = c(v3%,0) : G3 = c(v3%,1) : B3 = c(v3%,2)
        
              REM Texture coordinates:
              u1 = t(t1%,0) : v1 = t(t1%,1)
              u2 = t(t2%,0) : v2 = t(t2%,1)
              u3 = t(t3%,0) : v3 = t(t3%,1)
        
              REM Normals:
              IF n1% IF n2% IF n3% THEN
                p1 = 0 : q1 = 0 : r1 = 0
                p2 = 0 : q2 = 0 : r2 = 0
                p3 = 0 : q3 = 0 : r3 = 0
                ON ERROR LOCAL IF FALSE THEN
                  p(0) = n(n1%,0) : p(1) = n(n1%,1) : p(2) = n(n1%,2)
                  p() /= MOD(p()) : p1 = p(0) : q1 = p(1) : r1 = p(2)
                  p(0) = n(n2%,0) : p(1) = n(n2%,1) : p(2) = n(n2%,2)
                  p() /= MOD(p()) : p2 = p(0) : q2 = p(1) : r2 = p(2)
                  p(0) = n(n3%,0) : p(1) = n(n3%,1) : p(2) = n(n3%,2)
                  p() /= MOD(p()) : p3 = p(0) : q3 = p(1) : r3 = p(2)
                ENDIF : RESTORE ERROR
              ELSE
                a = x3 - x2
                b = y3 - y2
                c = z3 - z2
                d = x1 - x3
                e = y1 - y3
                f = z1 - z3
                p(0) = b*f-c*e
                p(1) = c*d-a*f
                p(2) = a*e-b*d
                IF MOD(p()) <> 0 p() /= MOD(p())
                p1 = p(0) : q1 = p(1) : r1 = p(2)
                p2 = p(0) : q2 = p(1) : r2 = p(2)
                p3 = p(0) : q3 = p(1) : r3 = p(2)
              ENDIF
        
              PROC4(FVFfile%,FN4(x1)) : PROC4(FVFfile%,FN4(y1)) : PROC4(FVFfile%,FN4(z1))
              PROC4(FVFfile%,FN4(p1)) : PROC4(FVFfile%,FN4(q1)) : PROC4(FVFfile%,FN4(r1))
              IF vf% AND &040 THEN
                BPUT#FVFfile%,B1*&FF
                BPUT#FVFfile%,G1*&FF
                BPUT#FVFfile%,R1*&FF
                BPUT#FVFfile%,&FF
              ENDIF
              IF vf% AND &100 PROC4(FVFfile%,FN4(u1)) : PROC4(FVFfile%,FN4(v1))
        
              PROC4(FVFfile%,FN4(x2)) : PROC4(FVFfile%,FN4(y2)) : PROC4(FVFfile%,FN4(z2))
              PROC4(FVFfile%,FN4(p2)) : PROC4(FVFfile%,FN4(q2)) : PROC4(FVFfile%,FN4(r2))
              IF vf% AND &040 THEN
                BPUT#FVFfile%,B2*&FF
                BPUT#FVFfile%,G2*&FF
                BPUT#FVFfile%,R2*&FF
                BPUT#FVFfile%,&FF
              ENDIF
              IF vf% AND &100 PROC4(FVFfile%,FN4(u2)) : PROC4(FVFfile%,FN4(v2))
        
              PROC4(FVFfile%,FN4(x3)) : PROC4(FVFfile%,FN4(y3)) : PROC4(FVFfile%,FN4(z3))
              PROC4(FVFfile%,FN4(p3)) : PROC4(FVFfile%,FN4(q3)) : PROC4(FVFfile%,FN4(r3))
              IF vf% AND &040 THEN
                BPUT#FVFfile%,B3*&FF
                BPUT#FVFfile%,G3*&FF
                BPUT#FVFfile%,R3*&FF
                BPUT#FVFfile%,&FF
              ENDIF
              IF vf% AND &100 PROC4(FVFfile%,FN4(u3)) : PROC4(FVFfile%,FN4(v3))
        
              xsum += x1 + x2 + x3
              ysum += y1 + y2 + y3
              zsum += z1 + z2 + z3
              nf% += 1
        
              v2% = v3% : t2% = t3% : n2% = n3%
            UNTIL FALSE
        ENDCASE
      ENDWHILE
      CLOSE #ObjFile%

      PRINT "Total number of vertices = "; nv%
      PRINT "Total number of texture coordinates = "; nt%
      PRINT "Total number of normals = "; nn%
      PRINT "Total number of faces = "; nf%
      PRINT "Mean X = "; xsum / nf% / 3
      PRINT "Mean Y = "; ysum / nf% / 3
      PRINT "Mean Z = "; zsum / nf% / 3

      vs% = 24 : REM Vertex size (bytes)
      IF vf% AND &040 vs% += 4
      IF vf% AND &100 vs% += 8

      PTR#FVFfile% = 0
      PROC4(FVFfile%, nf%*3)
      PROC4(FVFfile%, vf% OR vs% << 16)
      CLOSE #FVFfile%

      PRINT "Output file '"; FVFfile$; "' created"
      END

      DEF FNn(RETURN a$)
      LOCAL n
      IF ASCa$=&2F a$ = MID$(a$,2)
      n = VAL(a$)
      WHILE ASCa$=&20 : a$ = MID$(a$,2) : ENDWHILE
      WHILE ASCa$>&20 AND ASCa$<>&2F a$ = MID$(a$,2) : ENDWHILE
      WHILE ASCa$=&20 : a$ = MID$(a$,2) : ENDWHILE
      = n

      DEF PROC4(F%,N%)
      BPUT#F%,N% : BPUT#F%,N%>>8 : BPUT#F%,N%>>16 : BPUT#F%,N%>>24
      ENDPROC

      DEF FN4(a#)LOCALP%:P%=^a#:IFa#=FALSE THEN=FALSE
      =P%!4ANDNOT&7FFFFFFFOR(P%!4-&38000000<<3OR!P%>>29AND7)+(!P%>>28AND1)
DDRM

Re: New version of obj2fvf.bbc

Post by DDRM »

Update: Richard tells me this version will not work for rendering lit textures in BB4W, but will work for BBC-SDL. Update for BB4W in due course...

Best wishes,

D
guest

Re: New version of obj2fvf.bbc

Post by guest »

It was recently brought to my attention that OBJ2FVF does not take account of the different 3D coordinate systems in use. Direct3D (and therefore D3DLIB and, for compatibility reasons, BBCSDL's ogllib library) uses a left-handed coordinate system: the positive Z direction is away from the eye/camera, in the conventional orientation where Y is upwards and X points to the right. The Wavefront OBJ files accepted by OBJ2FVF use the 'industry standard' right-handed coordinate system (as does OpenGL, usually) in which the positive Z direction is towards the eye/camera.

You will often not notice the effect of this difference: the 3D model represented by the FVF file will be a 'mirror image' of what it should be, but objects are often symmetrical or not particularly sensitive to this reversal. For example the Tin Soldier model used by soldiers.bbc (in the examples/sounds/ folder) doesn't look any different as a result. But it will matter when the object itself, or perhaps more likely the texture applied to the object, must not be reversed; this will particularly be the case if it includes text or recognisable shapes (perhaps a map of the world!).

I have therefore modified the OBJ2FVF utility to reverse the sign of the Z-coordinate. If you are happy with your current FVF file there is nothing to be gained by repeating the conversion (indeed it would almost certainly require changes to the BBC BASIC program rendering it) but new conversions should use this new version (sorry that the input filename is 'hard coded', I was too lazy to add a file selector):

Code: Select all

      REM Wavefront OBJ to FVF converter, Richard Russell, 27-Nov-2018
This version will still not create suitable files for D3DLIB if lighting is used and if the OBJ file does not include RGB information. Direct3D seems to need vertex colours in order for the lighting to have an effect (although it may be possible to disable this feature). It's easy to hack the above code to force it to add RGB values even if none are present in the input file, but it doesn't do that by default because of the impact on file size.
guest

Re: New version of obj2fvf.bbc

Post by guest »

I've found yet another (stupid) bug in obj2fvf, here's a fixed version (it would still be nice for somebody to take this on and develop it into a proper utility):

Code: Select all

      REM Wavefront OBJ to FVF converter, Richard Russell, 11-Dec-2018
      HIMEM = PAGE + 200000000

      ObjFile$ = "..\soldier.obj"
      ObjFile% = OPENIN(ObjFile$)
      IF ObjFile%=0 ERROR 100, "Can't open file " + ObjFile$

      IF RIGHT$(ObjFile$,4)=".obj" OR RIGHT$(ObjFile$,4)=".OBJ" THEN
        FVFfile$ = LEFT$(ObjFile$,LEN(ObjFile$)-4) + ".fvf"
      ELSE
        FVFfile$ = ObjFile$ + ".obj"
      ENDIF
      FVFfile% = OPENOUT(FVFfile$)
      IF FVFfile%=0 ERROR 100, "Can't create file " + FVFfile$
      PTR#FVFfile% = 8

      DIM c(1000000,2), v(1000000,2), t(1000000,1), n(1000000,2), p(2)

      nv% = 0    : REM Number of vertices
      nt% = 0    : REM Number of texture coordinates
      nn% = 0    : REM Number of normals
      nf% = 0    : REM Number of triangular faces
      vf% = &012 : REM Initial vertex format
      xsum = 0
      ysum = 0
      zsum = 0

      WHILE NOT EOF#ObjFile%
        a$ = GET$#ObjFile%
        type$ = LEFT$(a$,2)
        a$ = MID$(a$,3)
        CASE type$ OF
          WHEN "v ":
            v(nv%,0) = FNn(a$) : v(nv%,1) = FNn(a$) : v(nv%,2) = FNn(a$)
            c(nv%,0) = FNn(a$) : c(nv%,1) = FNn(a$) : c(nv%,2) = FNn(a$)
            IF c(nv%,1) <> 0 OR c(nv%,2) <> 0 vf% OR= &040 : REM Include diffuse colour
            nv% += 1
          WHEN "vt":
            t(nt%,0) = FNn(a$) : t(nt%,1) = FNn(a$)
            IF t(nt%,0) <> 0 OR t(nt%,1) <> 0 vf% OR= &100 : REM Include texture coords
            nt% += 1
          WHEN "vn":
            n(nn%,0) = FNn(a$) : n(nn%,1) = FNn(a$) : n(nn%,2) = FNn(a$)
            nn% += 1
          WHEN "f ":
            t1% = 0 : t2% = 0 : t3% = 0 : n1% = 0 : n2% = 0 : n3% = 0
            v1% = FNn(a$) : IF ASCa$=&2F t1% = FNn(a$) : IF ASCa$=&2F n1% = FNn(a$)
            v2% = FNn(a$) : IF ASCa$=&2F t2% = FNn(a$) : IF ASCa$=&2F n2% = FNn(a$)
            IF v1% > 0 v1% -= 1 ELSE v1% += nv%
            IF v2% > 0 v2% -= 1 ELSE v2% += nv%
            IF t1% > 0 t1% -= 1 ELSE t1% += nt%
            IF t2% > 0 t2% -= 1 ELSE t2% += nt%
            IF n1% > 0 n1% -= 1 ELSE n1% += nn%
            IF n2% > 0 n2% -= 1 ELSE n2% += nn%
            REPEAT
              v3% = FNn(a$) : IF ASCa$=&2F t3% = FNn(a$) : IF ASCa$=&2F n3% = FNn(a$)
              IF v3% = 0 EXIT REPEAT
        
              IF v3% > 0 v3% -= 1 ELSE v3% += nv%
              IF t3% > 0 t3% -= 1 ELSE t3% += nt%
              IF n3% > 0 n3% -= 1 ELSE n3% += nn%
        
              REM Vertex coordinates:
              x1 = v(v1%,0) : y1 = v(v1%,1) : z1 = v(v1%,2)
              x2 = v(v2%,0) : y2 = v(v2%,1) : z2 = v(v2%,2)
              x3 = v(v3%,0) : y3 = v(v3%,1) : z3 = v(v3%,2)
        
              REM Vertex colour:
              R1 = c(v1%,0) : G1 = c(v1%,1) : B1 = c(v1%,2)
              R2 = c(v2%,0) : G2 = c(v2%,1) : B2 = c(v2%,2)
              R3 = c(v3%,0) : G3 = c(v3%,1) : B3 = c(v3%,2)
        
              REM Texture coordinates:
              u1 = t(t1%,0) : v1 = t(t1%,1)
              u2 = t(t2%,0) : v2 = t(t2%,1)
              u3 = t(t3%,0) : v3 = t(t3%,1)
        
              REM Normals:
              IF n1% IF n2% IF n3% THEN
                p1 = 0 : q1 = 0 : r1 = 0
                p2 = 0 : q2 = 0 : r2 = 0
                p3 = 0 : q3 = 0 : r3 = 0
                ON ERROR LOCAL IF FALSE THEN
                  p(0) = n(n1%,0) : p(1) = n(n1%,1) : p(2) = n(n1%,2)
                  p() /= MOD(p()) : p1 = p(0) : q1 = p(1) : r1 = p(2)
                  p(0) = n(n2%,0) : p(1) = n(n2%,1) : p(2) = n(n2%,2)
                  p() /= MOD(p()) : p2 = p(0) : q2 = p(1) : r2 = p(2)
                  p(0) = n(n3%,0) : p(1) = n(n3%,1) : p(2) = n(n3%,2)
                  p() /= MOD(p()) : p3 = p(0) : q3 = p(1) : r3 = p(2)
                ENDIF : RESTORE ERROR
              ELSE
                a = x3 - x2
                b = y3 - y2
                c = z3 - z2
                d = x1 - x3
                e = y1 - y3
                f = z1 - z3
                p(0) = b*f-c*e
                p(1) = c*d-a*f
                p(2) = a*e-b*d
                IF MOD(p()) <> 0 p() /= MOD(p())
                p1 = p(0) : q1 = p(1) : r1 = p(2)
                p2 = p(0) : q2 = p(1) : r2 = p(2)
                p3 = p(0) : q3 = p(1) : r3 = p(2)
              ENDIF
        
              PROC4(FVFfile%,FN4(x1)) : PROC4(FVFfile%,FN4(y1)) : PROC4(FVFfile%,FN4(-z1))
              PROC4(FVFfile%,FN4(p1)) : PROC4(FVFfile%,FN4(q1)) : PROC4(FVFfile%,FN4(-r1))
              IF vf% AND &040 THEN
                BPUT#FVFfile%,B1*&FF
                BPUT#FVFfile%,G1*&FF
                BPUT#FVFfile%,R1*&FF
                BPUT#FVFfile%,&FF
              ENDIF
              IF vf% AND &100 PROC4(FVFfile%,FN4(u1)) : PROC4(FVFfile%,FN4(v1))
        
              PROC4(FVFfile%,FN4(x2)) : PROC4(FVFfile%,FN4(y2)) : PROC4(FVFfile%,FN4(-z2))
              PROC4(FVFfile%,FN4(p2)) : PROC4(FVFfile%,FN4(q2)) : PROC4(FVFfile%,FN4(-r2))
              IF vf% AND &040 THEN
                BPUT#FVFfile%,B2*&FF
                BPUT#FVFfile%,G2*&FF
                BPUT#FVFfile%,R2*&FF
                BPUT#FVFfile%,&FF
              ENDIF
              IF vf% AND &100 PROC4(FVFfile%,FN4(u2)) : PROC4(FVFfile%,FN4(v2))
        
              PROC4(FVFfile%,FN4(x3)) : PROC4(FVFfile%,FN4(y3)) : PROC4(FVFfile%,FN4(-z3))
              PROC4(FVFfile%,FN4(p3)) : PROC4(FVFfile%,FN4(q3)) : PROC4(FVFfile%,FN4(-r3))
              IF vf% AND &040 THEN
                BPUT#FVFfile%,B3*&FF
                BPUT#FVFfile%,G3*&FF
                BPUT#FVFfile%,R3*&FF
                BPUT#FVFfile%,&FF
              ENDIF
              IF vf% AND &100 PROC4(FVFfile%,FN4(u3)) : PROC4(FVFfile%,FN4(v3))
        
              xsum += x1 + x2 + x3
              ysum += y1 + y2 + y3
              zsum += z1 + z2 + z3
              nf% += 1
        
              v2% = v3% : t2% = t3% : n2% = n3%
            UNTIL FALSE
        ENDCASE
      ENDWHILE
      CLOSE #ObjFile%

      PRINT "Total number of vertices = "; nv%
      PRINT "Total number of texture coordinates = "; nt%
      PRINT "Total number of normals = "; nn%
      PRINT "Total number of faces = "; nf%
      PRINT "Mean X = "; xsum / nf% / 3
      PRINT "Mean Y = "; ysum / nf% / 3
      PRINT "Mean Z = "; zsum / nf% / 3

      vs% = 24 : REM Vertex size (bytes)
      IF vf% AND &040 vs% += 4
      IF vf% AND &100 vs% += 8

      PTR#FVFfile% = 0
      PROC4(FVFfile%, nf%*3)
      PROC4(FVFfile%, vf% OR vs% << 16)
      CLOSE #FVFfile%

      PRINT "Output file '"; FVFfile$; "' created"
      END

      DEF FNn(RETURN a$)
      LOCAL n
      IF ASCa$=&2F a$ = MID$(a$,2)
      n = VAL(a$)
      WHILE ASCa$=&20 : a$ = MID$(a$,2) : ENDWHILE
      WHILE ASCa$>&20 AND ASCa$<>&2F a$ = MID$(a$,2) : ENDWHILE
      WHILE ASCa$=&20 : a$ = MID$(a$,2) : ENDWHILE
      = n

      DEF PROC4(F%,N%)
      BPUT#F%,N% : BPUT#F%,N%>>8 : BPUT#F%,N%>>16 : BPUT#F%,N%>>24
      ENDPROC

      DEF FN4(a#)LOCALP%:P%=^a#:IFABSa#<1E-38THEN=FALSE
      =P%!4ANDNOT&7FFFFFFFOR(P%!4-&38000000<<3OR!P%>>29AND7)+(!P%>>28AND1)