Topics

Wavefront OBJ to FVF converter

Richard Russell
 

I was fascinated by the 3D Face Reconstruction work by the University of Nottingham and wanted to experiment with manipulating the generated 3D models in BBC BASIC. These models are downloadable as Wavefront OBJ format files, whereas of course BBC BASIC expects a Flexible Vertex Format (FVF) file; so I wrote a converter!  You can see the rather spooky result at the Facebook page.  Here's another example of a converted OBJ file, this time of an inanimate object.

Richard.

Richard Russell
 

This has come up before, but as can so easily happen it seems to have slipped out of people's consciousness.  In the early days of BBC BASIC for Windows, when the D3DLIB library was first written, I used the extension .B3D for the 3D object files it uses.  I didn't realise (but should have done) that this extension had already been adopted by Blitz BASIC, and because theirs is also a format - albeit entirely different - for describing 3D objects the opportunity for confusion was very real!

So since then I have stopped using the .B3D extension and instead use .FVF (for Flexible Vertex Format, the Microsoft data structure that is used in the file).  That's not unique either (pretty much every 3-letter extension has already been used by somebody) but I don't think other uses are in the context of 3D object files.  In order not to perpetuate the confusion, can I ask that if you create or use the kind of 3D object file accepted by FN_load3D (in D3DLIBA, OGLLIB or GLESLIB) you preferentially use the extension .FVF rather than .B3D.

Richard.

Richard Russell
 

If anybody else wants to try the 3D Face Reconstruction tool, and animate the result in BBC BASIC, I've put the instructions here.  I look forward to seeing the results!

Richard.

Andrew Cool
 

Hello Richard,

The 3D Face Reconstruction tool is quite amazing.

I've tried to extend your code by saving out the animated frames to BMP or JPEG files, but get the error GDI+ error 7
from :-
      rem Create the GDI+ bitmap from the image handle
      sys `GdipCreateBitmapFromHBITMAP`, hbitmap%, 0, ^lBitmap% to lRes%
      if lRes% error 100, "GDI+ error "+str$(lRes%)

I've tried passing in both @hwnd% and pDev%, but both cause the same error message. Clearly neither is the hbitmap% that
procsavejpeg requires as its input parameter.

What variable name should I be passing in to capture the D3D screen contents?

Regards,

Andrew



Richard Russell
 

On Mon, Sep 18, 2017 at 09:12 pm, Andrew Cool wrote:
I've tried to extend your code by saving out the animated frames to BMP or JPEG files, but get the error GDI+ error 7
Just to be clear, you've tried both 'Method 1' (PROCcapturewindow1) and 'Method 2' (PROCcapturewindow2) and neither of them work, is that right?

I know that my Wiki article states that they should be suitable for capturing a Direct3D window, but I wouldn't be surprised if I've only tested it on Windows XP and a Google search found this which does state that the simple 'blit' method isn't reliable on Vista and later.  It lists an alternative in C# but converting that to BBC BASIC would not be very straightforward.

Another possibility might be to run BBCSDL instead of BB4W because that uses OpenGL for rendering rather than Direct3D. I've never attempted it myself but the glReadPixels API looks easy enough to call from BASIC judging by the description given.

Richard.

Andrew Cool
 

Yes, tried both methods and *screensave to boot. Output files are reproduced bit they are

either a 1x1 pixel black window, or the entire pixmap (1920x1440) in Black :-(

May have to revert to some 3rd party screen capture program to achieve this.

Running Win 7 64bit.

Andrew

Richard Russell
 

On Wed, Sep 20, 2017 at 12:57 am, Andrew Cool wrote:
Running Win 7 64bit.
Well I don't know what's going on.  I tried it here (with PROCcapturewindow2 and PROCsavejpeg) on a machine with the same spec and it worked absolutely fine for me, saving the image you can see here.  I really don't understand why it didn't work for you and would suggest that you repeat the exercise just to be sure it wasn't 'finger trouble'.

Richard.

Andrew Cool
 

>repeat the exercise just to be sure it wasn't 'finger trouble'.

More likely "brain" trouble with me ;-)

Below is what I've tried with your code. You'll have to change the fvf file back to POTUS.

Andrew

      REM. Turning Face by Richard Russell, 16-Sep-2017



      REM http://www.cs.nott.ac.uk/~psxasj/3dme/index.php


      HIMEM = PAGE + 5000000
      INSTALL @lib$+"D3DLIBA"

      ON CLOSE PROCcleanup : QUIT
      ON ERROR PROCcleanup : MODE 3 : PRINT REPORT$ : END
      ON MOVE IF @msg% <> 5 RETURN ELSE PROCcleanup : CLEAR
      VDU 20,26,12

      DIM pVB%(0), nv%(0), vf%(0), vl%(0), l%(0), m%(0), t%(0), y(0), p(0), r(0)
      DIM X(0), Y(0), Z(0), eye(2), at(2)

      REM. Initialise OpenGL:
      IF INKEY$(-256)="W" pDev% = FN_initd3d(@hwnd%, 1, 0) ELSE pDev% = FN_initgl(@hwnd%, 1, 0)
      IF pDev% = 0 ERROR 100, "Couldn't initialise 3D subsystem"



      pVB%(0) = FN_load3d(pDev%, @dir$ + "josh.fvf", nv%(0), vf%(0), vl%(0))


      REM pVB%(0) = fn_load3d(pDev%, @dir$ + "trump.fvf", nv%(0), vf%(0), vl%(0))

      IF pVB%(0) = 0 ERROR 101, "Couldn't load FVF file"

      REM. Render the turning face:
      at() = 100, 100, 40
      frame_count = 0
      REPEAT
        turn = SIN(TIME/100)
        eye() = at(0)+400*SIN(turn), 0, at(2)+400*COS(turn)
        PROC_render(pDev%, &7F7F90, 0, l%(), 1, m%(), t%(), pVB%(), nv%(), vf%(), vl%(), \
        \ y(), p(), r(), X(), Y(), Z(), eye(), at(), PI/6, @vdu%!208/@vdu%!212, 1, 1000, PI)
 
 
        file$ = @dir$ + "Frame_"+ STR$(frame_count) + ".bmp"
 
        REM file$ = @dir$ + "Frame_"+ STR$(frame_count) + ".jpg"
 
 
        REM PROCcapturewindow1(@hwnd%, file$)
        PROCcapturewindow1(pDev%, file$)
        REM PROCcapturewindow2(@hwnd%, file$)
        REM PROCcapturewindow1(@hwnd%, file$)
        REM PROCcapturewindow2(@hwnd%, file$)
 
 
        REM PROCsavejpeg(@hwnd%, file$, 100)
 
 
        frame_count = frame_count + 1
      UNTIL INKEY(1)=0
      END

      DEF PROCcleanup
      pVB%(0) += 0  : IF pVB%(0)  PROC_release(pVB%(0))
      *REFRESH ON
      ENDPROC


      REM  right$(log Last Edit: Yesterday at 6:05pm by Richard Russell right$(val

      DEF PROCsavejpeg(hbitmap%, filename$, quality%)
      LOCAL gdiplus%, ole32%

      SYS "LoadLibrary", "GDIPLUS.DLL" TO gdiplus%
      IF gdiplus% = 0 ERROR 100, "Couldn't load GDIPLUS.DLL"
      SYS "GetProcAddress", gdiplus%, "GdiplusStartup" TO `GdiplusStartup`
      SYS "GetProcAddress", gdiplus%, "GdiplusShutdown" TO `GdiplusShutdown`
      SYS "GetProcAddress", gdiplus%, "GdipCreateBitmapFromHBITMAP" TO `GdipCreateBitmapFromHBITMAP`
      SYS "GetProcAddress", gdiplus%, "GdipDisposeImage" TO `GdipDisposeImage`
      SYS "GetProcAddress", gdiplus%, "GdipSaveImageToFile" TO `GdipSaveImageToFile`
      SYS "LoadLibrary", "OLE32.DLL" TO ole32%
      SYS "GetProcAddress", ole32%, "CLSIDFromString" TO `CLSIDFromString`

      LOCAL tSI{}, tParams{}, lRes%, lGDIP%, lBitmap%, tJpgEncoder%, guid%, filename%

      DIM tJpgEncoder% LOCAL 15, guid% LOCAL 79, filename% LOCAL 2*LEN(filename$)+1
      DIM tParams{Count%, Guid&(15), NumberOfValues%, Type%, Value%}
      DIM tSI{GdiplusVersion%, DebugEventCallback%, \
      \       SuppressBackgroundThread%, SuppressExternalCodecs%}

      REM Initialize GDI+
      tSI.GdiplusVersion% = 1
      SYS `GdiplusStartup`, ^lGDIP%, tSI{}, 0 TO lRes%
      IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%)

      REM Create the GDI+ bitmap from the image handle
      SYS `GdipCreateBitmapFromHBITMAP`, hbitmap%, 0, ^lBitmap% TO lRes%
      IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%)

      REM Initialize the encoder GUID
      SYS "MultiByteToWideChar", 0, 0, "{557CF401-1A04-11D3-9A73-0000F81EF32E}", -1, guid%, 40
      SYS `CLSIDFromString`, guid%, tJpgEncoder%

      REM Initialize the encoder parameters
      tParams.Count% = 1
      SYS "MultiByteToWideChar", 0, 0, "{1D5BE4B5-FA4A-452D-9CDD-5DB35105E7EB}", -1, guid%, 40
      SYS `CLSIDFromString`, guid%, ^tParams.Guid&(0)
      tParams.NumberOfValues% = 1
      tParams.Type% = 4
      tParams.Value% = ^quality%

      REM Save the image
      SYS "MultiByteToWideChar", 0, 0, filename$, -1, filename%, LEN(filename$)+1
      SYS `GdipSaveImageToFile`, lBitmap%, filename%, tJpgEncoder%, tParams{} TO lRes%
      IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%)

      REM Destroy the bitmap
      SYS `GdipDisposeImage`, lBitmap%

      REM Shutdown GDI+
      SYS `GdiplusShutdown`, lGDIP%
      SYS "FreeLibrary", gdiplus%

      ENDPROC


      DEF PROCcapturewindow1(hwnd%, file$)
      PRF_CHILDREN = 16
      PRF_ERASEBKGND = 8
      PRF_CLIENT = 4
      PRF_NONCLIENT = 2
      WM_PRINT = 791
      LOCAL rc{}, hdc%, hbm%, oldbm%
      DIM rc{l%,t%,r%,b%}
      SYS "GetWindowRect", hwnd%, rc{}
      SYS "CreateCompatibleDC", @memhdc% TO hdc%
      SYS "CreateCompatibleBitmap", @memhdc%, rc.r%-rc.l%, rc.b%-rc.t% TO hbm%
      SYS "SelectObject", hdc%, hbm% TO oldbm%
      SYS "SendMessage", hwnd%, WM_PRINT, hdc%, \
      \                  PRF_CHILDREN+PRF_ERASEBKGND+PRF_CLIENT+PRF_NONCLIENT
      SYS "SelectObject", hdc%, oldbm%
      PROCsaveasbmp(hbm%, file$)
      SYS "DeleteObject", hbm%
      SYS "DeleteDC", hdc%
      ENDPROC


      DEF PROCcapturewindow2(hwnd%, file$)
      LOCAL rc{}, hdc%, hbm%, ddc%, oldbm%
      DIM rc{l%,t%,r%,b%}
      SYS "GetWindowRect", hwnd%, rc{}
      SYS "CreateDC", "DISPLAY", 0, 0, 0 TO ddc%
      SYS "CreateCompatibleDC", @memhdc% TO hdc%
      SYS "CreateCompatibleBitmap", @memhdc%, rc.r%-rc.l%, rc.b%-rc.t% TO hbm%
      SYS "SelectObject", hdc%, hbm% TO oldbm%
      SYS "BitBlt", hdc%, 0, 0, rc.r%-rc.l%, rc.b%-rc.t%, ddc%, rc.l%, rc.t%, &CC0020
      SYS "SelectObject", hdc%, oldbm%
      PROCsaveasbmp(hbm%, file$)
      SYS "DeleteObject", hbm%
      SYS "DeleteDC", hdc%
      SYS "DeleteDC", ddc%
      ENDPROC



      DEF PROCsaveasbmp(hbm%,file$)
      LOCAL bmp%, ZWIDTH%, height%, size%, ZDATA%, res%

      REM. Find the bitmap dimensions and file size:
      DIM bmp% LOCAL 26
      bmp% = (bmp% + 3) AND -4
      SYS "GetObject", hbm%, 24, bmp% TO res%
      IF res%=0 ERROR 100, "GetObject failed"

      ZWIDTH% = bmp%!4
      height% = bmp%!8
      size% = 54 + height%*((ZWIDTH%*3 + 3) AND -4)

      REM. Allocate and zero memory for BMP file:
      SYS "GlobalAlloc", &40, size% TO ZDATA%
      IF ZDATA%=0 ERROR 100, "GlobalAlloc failed"

      REM. Store file and bitmap headers:
      ZDATA%?0 = ASC"B"
      ZDATA%?1 = ASC"M"
      ZDATA%!2 = size%
      ZDATA%!10 = 54
      ZDATA%!14 = 40
      ZDATA%!18 = ZWIDTH%
      ZDATA%!22 = height%
      ZDATA%!26 = &180001

      REM. Copy the image into the DIB:
      SYS "GetDIBits", @memhdc%, hbm%, 0, height%, ZDATA%+54, ZDATA%+14, 0 TO res%
      IF res%<>height% ERROR 100, "GetDIBits failed"

      REM. Save the output file:
      OSCLI "SAVE """+file$+""" "+STR$~ZDATA%+" +"+STR$~size%

      SYS "GlobalFree", ZDATA%
      ENDPROC

Richard Russell
 

On Wed, Sep 20, 2017 at 10:42 pm, Andrew Cool wrote:
Below is what I've tried with your code.
That won't work, as listed, because you're using PROCcapturewindow1; I'm pretty certain that you must use PROCcapturewindow2 to capture Direct3D output.  However you said that you had tried both capture methods, and I can't see any reason why it would not have successfully saved a BMP when using method 2.

As you know I am unhappy about listing lengthy code in a group post - the forum is a better place for that - but on this occasion I'll break my own rule and list the code that worked for me.  But it's basically the same as yours apart from saving a JPG rather than a BMP:

      REM. Turning Face by Richard Russell, 16-Sep-2017

      HIMEM = PAGE + 5000000
      IF INKEY$(-256)="W" INSTALL @lib$+"D3DLIBA" ELSE INSTALL @lib$+"ogllib"

      ON CLOSE PROCcleanup : QUIT
      ON ERROR PROCcleanup : MODE 3 : PRINT REPORT$ : END
      ON MOVE IF @msg% <> 5 RETURN ELSE PROCcleanup : CLEAR
      VDU 20,26,12

      DIM pVB%(0), nv%(0), vf%(0), vl%(0), l%(0), m%(0), t%(0), y(0), p(0), r(0)
      DIM X(0), Y(0), Z(0), eye(2), at(2)

      REM. Initialise 3D subsystem:
      IF INKEY$(-256)="W" pDev% = FN_initd3d(@hwnd%, 1, 0) ELSE pDev% = FN_initgl(@hwnd%, 1, 0)
      IF pDev% = 0 ERROR 100, "Couldn't initialise 3D subsystem"

      REM. Load 3D object:
      pVB%(0) = FN_load3d(pDev%, @dir$ + "trump.fvf", nv%(0), vf%(0), vl%(0))
      IF pVB%(0) = 0 ERROR 101, "Couldn't load FVF file"

      REM. Render the turning face:
      at() = 100, 100, 40
      REPEAT
        turn = SIN(TIME/100)
        eye() = at(0)+400*SIN(turn), 0, at(2)+400*COS(turn)
        PROC_render(pDev%, &7F7F90, 0, l%(), 1, m%(), t%(), pVB%(), nv%(), vf%(), vl%(), \
        \ y(), p(), r(), X(), Y(), Z(), eye(), at(), PI/6, @vdu%!208/@vdu%!212, 1, 1000, PI)
        IF INKEY(1) = 32 PROCcapturewindow2(@hwnd%, @dir$+"direct3d.jpg")
      UNTIL FALSE
      END

      DEF PROCcleanup
      pVB%(0) += 0  : IF pVB%(0)  PROC_release(pVB%(0))
      *REFRESH ON
      ENDPROC

      DEF PROCcapturewindow2(hwnd%, file$)
      LOCAL rc{}, hdc%, hbm%, ddc%, oldbm%
      DIM rc{l%,t%,r%,b%}
      SYS "GetWindowRect", hwnd%, rc{}
      SYS "CreateDC", "DISPLAY", 0, 0, 0 TO ddc%
      SYS "CreateCompatibleDC", @memhdc% TO hdc%
      SYS "CreateCompatibleBitmap", @memhdc%, rc.r%-rc.l%, rc.b%-rc.t% TO hbm%
      SYS "SelectObject", hdc%, hbm% TO oldbm%
      SYS "BitBlt", hdc%, 0, 0, rc.r%-rc.l%, rc.b%-rc.t%, ddc%, rc.l%, rc.t%, &CC0020
      SYS "SelectObject", hdc%, oldbm%
      PROCsavejpeg(hbm%, file$, 50)
      SYS "DeleteObject", hbm%
      SYS "DeleteDC", hdc%
      SYS "DeleteDC", ddc%
      ENDPROC

      DEF PROCsavejpeg(hbitmap%, filename$, quality%)
      LOCAL gdiplus%, ole32%
      SYS "LoadLibrary", "GDIPLUS.DLL" TO gdiplus%
      IF gdiplus% = 0 ERROR 100, "Couldn't load GDIPLUS.DLL"
      SYS "GetProcAddress", gdiplus%, "GdiplusStartup" TO `GdiplusStartup`
      SYS "GetProcAddress", gdiplus%, "GdiplusShutdown" TO `GdiplusShutdown`
      SYS "GetProcAddress", gdiplus%, "GdipCreateBitmapFromHBITMAP" TO `GdipCreateBitmapFromHBITMAP`
      SYS "GetProcAddress", gdiplus%, "GdipDisposeImage" TO `GdipDisposeImage`
      SYS "GetProcAddress", gdiplus%, "GdipSaveImageToFile" TO `GdipSaveImageToFile`
      SYS "LoadLibrary", "OLE32.DLL" TO ole32%
      SYS "GetProcAddress", ole32%, "CLSIDFromString" TO `CLSIDFromString`
      LOCAL tSI{}, tParams{}, lRes%, lGDIP%, lBitmap%, tJpgEncoder%, guid%, filename%
      DIM tJpgEncoder% LOCAL 15, guid% LOCAL 79, filename% LOCAL 2*LEN(filename$)+1
      DIM tParams{Count%, Guid&(15), NumberOfValues%, Type%, Value%}
      DIM tSI{GdiplusVersion%, DebugEventCallback%, \
      \       SuppressBackgroundThread%, SuppressExternalCodecs%}
      REM Initialize GDI+
      tSI.GdiplusVersion% = 1
      SYS `GdiplusStartup`, ^lGDIP%, tSI{}, 0 TO lRes%
      IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%)
      REM Create the GDI+ bitmap from the image handle
      SYS `GdipCreateBitmapFromHBITMAP`, hbitmap%, 0, ^lBitmap% TO lRes%
      IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%)
      REM Initialize the encoder GUID
      SYS "MultiByteToWideChar", 0, 0, "{557CF401-1A04-11D3-9A73-0000F81EF32E}", -1, guid%, 40
      SYS `CLSIDFromString`, guid%, tJpgEncoder%
      REM Initialize the encoder parameters
      tParams.Count% = 1
      SYS "MultiByteToWideChar", 0, 0, "{1D5BE4B5-FA4A-452D-9CDD-5DB35105E7EB}", -1, guid%, 40
      SYS `CLSIDFromString`, guid%, ^tParams.Guid&(0)
      tParams.NumberOfValues% = 1
      tParams.Type% = 4
      tParams.Value% = ^quality%
      REM Save the image
      SYS "MultiByteToWideChar", 0, 0, filename$, -1, filename%, LEN(filename$)+1
      SYS `GdipSaveImageToFile`, lBitmap%, filename%, tJpgEncoder%, tParams{} TO lRes%
      IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%)
      REM Destroy the bitmap
      SYS `GdipDisposeImage`, lBitmap%
      REM Shutdown GDI+
      SYS `GdiplusShutdown`, lGDIP%
      SYS "FreeLibrary", gdiplus%
      ENDPROC