%%% File    : wpc_ex1.erl
%%% Author  : Dan Gudmundsson  dangud(a)gmail.com
%%% Description : An example of how to write simple exporter plugin to wings
%%%               It's a binary export of all triangles it includes normals and
%%%               uv-coordinates.
%%%               For an example of how to create textual format take look at
%%%               wpc_wrl.erl which is pretty short.
%%% Created : 11 Mar 2005 by Dan Gudmundsson

%% Welcome to the wonderful world of erlang boys and girls.
%% Put this file in dir wings/plugins_src/import_export/
%% Add the file to the Makefile 
%% invoke make and restart wings..and voila a new exporter have appeared

%%% Fileformat of the created files:
%%  Everything is 32 bits little endian, integers and floats
%%  all 'SizeOf' contains the number of bytes
%%  The 'Size Of Name' includes an ending \0
%%  We start of with a header chunk of 128 bytes size, it contains:
%%  a MAGICNUMBER, 2 spare bytes, Size of RawVertexChunk  and extra info bytes
%%  16#E15E 16#0000 TotSizeOfVertexData + 120 bytes of creation info
%%  Directly after the header comes 'TotSizeOfVertexData' bytes.
%%  It contains raw vertex data chunk for every object. 
%%     (each triangle looks like)
%%     V1x,V1y,V1z,N1x,N1y,N1z,S1,T1
%%     V2x,V2y,V2z,N2x,N2y,N2z,S2,T2
%%     V3x,V3y,V3z,N3x,N3y,N3z,S3,T3
%%  i.e. each Vertex have 8*4=32 bytes of info and each triangle have 32*3 bytes.
%%  After the vertexchunk comes a materialchunk it looks like this: 
%%  SizeOfMaterialChunk (in bytes, 32bits little endian)
%%  SizeOfMatname MatName\0
%%          DiffuseRGBA  
%%          AmbientRGBA  
%%          SpecularRGBA 
%%          EmissionRGBA 
%%          Shininess (float between 0.0-1.0)
%%          NumberOfTextureImages     
%%             SizeOfImageTypeName ImageTypeName\0 SizeOfFileName FileName\0
%%             SizeOfImageTypeName ImageTypeName\0 SizeOfFileName FileName\0
%%           Where ImageType is string describing the file i.e. diffuse,gloss,bump
%%  SizeOfMatName MatName2\0
%%     ....
%%  And the last block contains object information, each object is separated
%%  per material, so each object may contain several parts.
%%  A StartVertex of Zero means the that the block starts at VertexChunk
%%  TotSizeOfObjectChunk
%%  SizeOfThisObjectChunk SizeOfObjectName ObjectName\0 
%%      NumberOfMeshesInObject
%%        SizeOfMatName MatName\0 StartVertex NoOfVertices
%%        SizeOfMatName MatName\0 StartVertex NoOfVertices
%%        ....
%%  SizeOfThisObjectChunk SizeOfObjectName ObjectName\0 
%%      NumberOfMeshesInObject
%%        SizeOfMatName MatName\0 StartVertex NoOfVertices
%%        SizeOfMatName MatName\0 StartVertex NoOfVertices
%%        ....
%%  Comments about the format, I have choosen this because if the user
%%  ignores everything about materials and objects, it's very simple
%%  to parse. Read vertexchunk size in bytes 4-8 (an integer) skip
%%  120 bytes and read the vertex chunk and skip everything after 
%%  the vertexchunk size of bytes.

%%  I used the following (erlang) opengl vbo code when testing the exporter:
%%     [Buff] = gl:genBuffers(1),
%%     gl:bindBuffer(?GL_ARRAY_BUFFER,Buff),
%%     gl:bufferData(?GL_ARRAY_BUFFER, size(DataChunk), DataChunk, ?GL_STATIC_DRAW),
%%     gl:vertexPointer(3, ?GL_FLOAT,  8*4, 0),
%%     gl:normalPointer(?GL_FLOAT,     8*4, 3*4),
%%     gl:texCoordPointer(2,?GL_FLOAT, 8*4, 6*4),
%%     %% The acutal Drawing code is
%%      gl:enableClientState(?GL_VERTEX_ARRAY),
%%      gl:enableClientState(?GL_NORMAL_ARRAY),
%%      gl:enableClientState(?GL_TEXTURE_COORD_ARRAY),
%%      foreach(fun({Mat,First,Sz}) ->
%%               set_mat(Mat,Mats),
%%               gl:bindBuffer(?GL_ARRAY_BUFFER,Buff),
%%               gl:drawArrays(?GL_TRIANGLES, First, Sz),
%%               gl:bindBuffer(?GL_ARRAY_BUFFER,0)
%%              end, ObjRefs),
%%      gl:disableClientState(?GL_TEXTURE_COORD_ARRAY),
%%      gl:disableClientState(?GL_VERTEX_ARRAY),
%%      gl:disableClientState(?GL_NORMAL_ARRAY)

%% Now lets get on with the implementation of the exporter.

%% Module declaration, it defines the name of the this module.
%% and how it is accessed from other modules, must be the same
%% as the filename. Also for wings to find plugin it must start
%% with 'wpc_'

%% -export() defines which functions can be accessed from
%% other modules.
%% These are the functions that wings will call and must be exported
-export([init/0, menu/2, command/2]).

%% Importing can viewed as a shortcut that way we don't have write
%% the module 'lists' before each call to foreach,foldl,map
-import(lists, [foreach/2, foldl/3, map/2, reverse/1]).
%% proplists is really long to type import the used function
-import(proplists, [get_value/2,get_value/3]).

%% Some include files.

%% A define loooks like this.
-define(EXTENSION, wex1).

%%  Wings callback functions

%% This is first function that wings will call when wings starts, we
%% don't have to do any initialization work, just return true.
init() ->

%% Menu, these functions will be called each time the user opens
%% a menu from wings.

%% We want to add a menu entry in export menu at the bottom
menu({file, export}, Menu) ->
   Menu ++ [{"Example exporter (.wex1)...", ?EXTENSION, [option]}];
%% We don't care about other menues.
menu(_, Menu) -> Menu.

%% Command, these functions will be invoked by wings when a command
%% is invoke from the user, either by a menu or shortcut.

%% Lets export the objects if our command was invoked.
command({file,{export,{?EXTENSION,Ask}}}, St) ->
   init_export(Ask, St);
%% Skip all other commands..
command(_, _) -> next.

%%  Exporter initialization functions

%% If Ask is an atom the user wants to edit the options
%% open the option dialog.

%% We also define and give wpa:dialog a function to be invoked
%% with the result, that function just restarts the export with the
%% options.
init_export(Ask, _) when is_atom(Ask) ->
   Result = fun(Options) ->
   %% We want tga to be the default texture image format
   wpa:pref_set_default(?MODULE, default_filetype, ".tga"),
   %% We want to use the standard export dialog + a compress option
   %% but we don't want include(uvs/normal/vertexcolor) options exclude them 
   ExcludeOptions = [include_uvs,include_normals, include_colors],
   Dialog = [wpa:dialog_template(?MODULE, export,ExcludeOptions)],
   %% Everything done display the dialog
   wpa:dialog(Ask, "Export Options", Dialog, Result);

%% Ok we where invoked with some export options, the options
%% where either set with the dialog or picked from the preferences.
init_export(Options, St) ->
   %% Store the options in the preferences, so that next time it will
   %% be the default options.
   wpa:pref_set(?MODULE, Options),

   %% get_value/3 is an imported function (from proplists)
   SubDivs = get_value(subdivisions, Options, 0),
   %% Create the export options
   ExportOpts = [{subdivisions, SubDivs},
                 {ext, ".wex1"},{ext_desc, "Example exporter"}],

   %% Create the callback function which will be called from wings,
   %% it takes 2 arguments FileName and Contents
   ExportFun =
       fun(Filename, Contents) ->
               export_file(Filename, Contents, Options)
   %% Call wpa:export which will subdivide and triangulate the meshes
   %% and convert wings internal datastructures to a e3d mesh (see
   %% e3d.hrl) then it will call the callback function 'ExportFun'
   wpa:export(ExportOpts, ExportFun, St).

%%  The real export code comes here

export_file(Filename, Data0 = #e3d_file{}, Options) ->
   %% Transform data i.e. scale and/or rotate of Z
   Transform = wpa:export_matrix(Options),
   Data1 = e3d_file:transform(Data0, Transform),

   %% Export all textures..
   Filetype = get_value(default_filetype, Options, ".tga"),
   Data = wpa:save_images(Data1, filename:dirname(Filename), Filetype),

   %% Get the stuff we need from Data
   #e3d_file{objs=Objs,mat=Materials,creator=Creator} = Data,

       %% Create the raw mesh chunks
       {ObjInfo,ObjData} =  %% Foreach Object in objs 
               foldl(fun(#e3d_object{obj=Object,name=Name}, {ObjInfo,ObjData}) ->
                             %% Calculate smooth normals per vertex
                             ObjWithNormals = e3d_mesh:vertex_normals(Object),
                             %% Split the object per material
                             Meshes = e3d_mesh:split_by_material(ObjWithNormals),
                             %% Foreach Mesh in Meshes, create the Info and data
                                 = foldl(fun(Mesh, {MIIn,MDin}) ->
                                                 %% Each object have Info and Data
                                                 {MI,MD} = export_object(Mesh),
                                                 %% Add it to the rest
                                         {[],ObjData}, %% Output params
                                         Meshes), %% Loop over all meshes.
                             %% Rember the Name and MeshesInfo and the data
                     end, {[],[]}, Objs),

       %% Add all meshes together
       DataChunk = list_to_binary(ObjData),
       %% Convert Creator string to binary
       CreatorBin  = list_to_binary(Creator),
       %% Create the header chunk, init bytes, create info and fill up with \0
       HeaderChunk = <<16#E15E:16/little,16#0000:16/little,(size(DataChunk)):32/little,
                      CreatorBin/binary, 0:((120-size(CreatorBin))*8)>>,
       %% Create the material chunk
       MaterialChunk = export_materials(Materials,[]),
       %% Create Object reference chunk
       ObjectRefChunk = export_object_refs(ObjInfo, 0, []),

       %% Add it all together..
       FileContents = <<HeaderChunk/binary,DataChunk/binary,
       %% And write the lot to the file
       ok = file:write_file(Filename, FileContents)

   catch  %% Some error handling
       throw:Error ->
           io:format("Exporter failed: ~p~n", [Error]),
           ErrStr = io_lib:format("Exporter failed: ~s", [Error]),
         _:Error ->
           io:format("Exporter crashed: ~p ~p~n", [Error, erlang:get_stacktrace()]),
           ErrStr = io_lib:format("Exporter crashed: ~W", [Error,4]),

%% At last we can see the end of everything..
export_object(#e3d_mesh{vs=Vs0,ns=Ns0,tx=Uv0,fs=Fs}) ->
    %% Do some initialization,
    %% convert the lists to a tuple of floats
    Vs = convert_to_binary(Vs0, []),
    Ns = convert_to_binary(Ns0, []),
    Uv = convert_to_binary(Uv0, []),
    %% Here we go again for each face in Fs
    %% Create a binary that looks like
    %% <<V1x,V1y,V1z,N1x,N1y,N1z,U1,V1
    %%   V2x,V2y,V2z,N2x,N2y,N2z,U2,V2
    %%   V3x,V3y,V3z,N3x,N3y,N3z,U3,V3>>
    ListOfBinFs =
        map(fun(#e3d_face{vs=[V1,V2,V3],ns=[N1,N2,N3],tx=[UV1,UV2,UV3]}) ->
               (#e3d_face{tx=[]}) ->
                    throw("No UV coordinates available");
               (_) -> % Proberly not triangulated correctly
                    throw("Model not triangulated, report BUG in wings trianglutor")
            end, Fs),
    %% So we got a list of binaries convert that to a big binary..
    BinFs = list_to_binary(ListOfBinFs),
    %% Get the Material name since we have done a split by material
    %% each face will have the same material so pick the first faces
    %% material..
    [#e3d_face{mat=[Material|_]}|_] = Fs,
    %% Return the needed information {material, NoOfTriangles} and the binary

%% element is 1 counted, e3d starts at 0..arrg..add one before looking up the object..
idx(Idx, Table) ->
   element(Idx+1, Table).

%% This converts a list of tuples of floats to a tuple of binaries.
%% It's much faster do element(Index,Table) than a list search operation.
convert_to_binary([{X,Y,Z}|Rest], Prev) ->
   Converted = <<X:32/float-little,Y:32/float-little,Z:32/float-little>>,
   convert_to_binary(Rest, [Converted|Prev]);
convert_to_binary([{U,V}|Rest], Prev) ->
   Converted = <<U:32/float-little,V:32/float-little>>,
   convert_to_binary(Rest, [Converted|Prev]);
convert_to_binary([],Done) ->
   Ordered = reverse(Done),

%% With the first material
export_materials([{Name,Mat}|Mats],All) ->
    %% Get all info we need
    NameBin = name_to_binary(Name),
    OpMat = get_value(opengl, Mat),
    {DR,DG,DB,DA} = get_value(diffuse,OpMat),
    {AR,AG,AB,AA} = get_value(ambient,OpMat),
    {SR,SG,SB,SA} = get_value(specular,OpMat),
    {ER,EG,EB,EA} = get_value(emission,OpMat),
    Sh = get_value(shininess,OpMat),
    %% Get the texture images
    Maps = export_images(get_value(maps, Mat, []),[]), 
    %% Make the data for this material 
    MatBin = <<(size(NameBin)+1):32/little,NameBin/binary,0:8,
              Sh:32/float, Maps/binary>>,
    %% Remember this material and handle the rest of the materials
%% We have transformed all materials, create the materialchunk.
export_materials([],All) ->

%% Export images loops through each texture image and creates a file
%% reference per image. The actual image is already exported to
%% separate files
export_images([],All) ->
export_images([{Type,#e3d_image{filename=File}}|Maps],All) ->
    TypeBin = name_to_binary(Type),
    FileBin = name_to_binary(File),
    %% The raw-image data is available in #e3d_image{}, so If you want
    %% the actual image to be included in the exported file, you could
    %% add it here instead of a file reference.
    ImageRef = <<(size(TypeBin)+1):32/little,TypeBin/binary, 0:8,
                (size(FileBin)+1):32/little,FileBin/binary, 0:8>>,
    export_images(Maps, [ImageRef|All]).

%% Make a object chunk per object
export_object_refs([{ObjName,ObjInfo}|Chunks], Start, Add) ->
    %% Collect the information
    {NextPos,Mats} = export_refs(ObjInfo,Start,[]),
    ObjNameBin = name_to_binary(ObjName),
    %% Create the binary
    ObjectRef = <<(size(ObjNameBin)+1):32/little,ObjNameBin/binary,0:8,
    Size  = <<(size(ObjectRef)):32/little>>,
    %% Add the size in backwards order, the list is reverse in the last step.
    export_object_refs(Chunks, NextPos, [ObjectRef,Size|Add]);
export_object_refs([], _, ChunkList) ->

%% Foreach data chunk create a reference point: matName,startVertex,NoOfVertices
export_refs([{MatName,Triangles}|Next],Start,All) ->
    Mat = name_to_binary(MatName),
    Verts = Triangles*3,
    New = <<(size(Mat)+1):32/little,Mat/binary,0:8,Start:32/little,Verts:32/little>>,
    export_refs(Next, Start+Verts, [New|All]);
export_refs([],End,All) ->
    %% Remember to reverse the list so order is correct

%% A small help function.
name_to_binary(Name) when is_atom(Name) ->
name_to_binary(Name) when is_list(Name) ->
name_to_binary(Name) when is_binary(Name) ->