function OBJ=read_wobj(fullfilename) % Read the objects from a Wavefront OBJ file % % OBJ=read_wobj(filename); % % OBJ struct containing: % % OBJ.vertices : Vertices coordinates % OBJ.vertices_texture: Texture coordinates % OBJ.vertices_normal : Normal vectors % OBJ.vertices_point : Vertice data used for points and lines % OBJ.material : Parameters from external .MTL file, will contain parameters like % newmtl, Ka, Kd, Ks, illum, Ns, map_Ka, map_Kd, map_Ks, % example of an entry from the material object: % OBJ.material(i).type = newmtl % OBJ.material(i).data = 'vase_tex' % OBJ.objects : Cell object with all objects in the OBJ file, % example of a mesh object: % OBJ.objects(i).type='f' % OBJ.objects(i).data.vertices: [n x 3 double] % OBJ.objects(i).data.texture: [n x 3 double] % OBJ.objects(i).data.normal: [n x 3 double] % % Example, % OBJ=read_wobj('examples\example10.obj'); % FV.vertices=OBJ.vertices; % FV.faces=OBJ.objects(3).data.vertices; % figure, patch(FV,'facecolor',[1 0 0]); camlight % % Function is written by D.Kroon University of Twente (June 2010) verbose=true; if(exist('fullfilename','var')==0) [filename, filefolder] = uigetfile('*.obj', 'Read obj-file'); fullfilename = [filefolder filename]; end filefolder = fileparts( fullfilename); if(verbose),disp(['Reading Object file : ' fullfilename]); end % Read the DI3D OBJ textfile to a cell array file_words = file2cellarray( fullfilename); % Remove empty cells, merge lines split by "\" and convert strings with values to double [ftype fdata]= fixlines(file_words); % Vertex data vertices=[]; nv=0; vertices_texture=[]; nvt=0; vertices_point=[]; nvp=0; vertices_normal=[]; nvn=0; material=[]; % Surface data no=0; % Loop through the Wavefront object file for iline=1:length(ftype) if(mod(iline,10000)==0), if(verbose),disp(['Lines processed : ' num2str(iline)]); end end type=ftype{iline}; data=fdata{iline}; % Switch on data type line switch(type) case{'mtllib'} if(iscell(data)) datanew=[]; for i=1:length(data) datanew=[datanew data{i}]; if(i<length(data)), datanew=[datanew ' ']; end end data=datanew; end filename_mtl=fullfile(filefolder,data); material=readmtl(filename_mtl,verbose); case('v') % vertices nv=nv+1; if(length(data)==3) % Reserve block of memory if(mod(nv,10000)==1), vertices(nv+1:nv+10001,1:3)=0; end % Add to vertices list X Y Z vertices(nv,1:3)=data; else % Reserve block of memory if(mod(nv,10000)==1), vertices(nv+1:nv+10001,1:4)=0; end % Add to vertices list X Y Z W vertices(nv,1:4)=data; end case('vp') % Specifies a point in the parameter space of curve or surface nvp=nvp+1; if(length(data)==1) % Reserve block of memory if(mod(nvp,10000)==1), vertices_point(nvp+1:nvp+10001,1)=0; end % Add to vertices point list U vertices_point(nvp,1)=data; elseif(length(data)==2) % Reserve block of memory if(mod(nvp,10000)==1), vertices_point(nvp+1:nvp+10001,1:2)=0; end % Add to vertices point list U V vertices_point(nvp,1:2)=data; else % Reserve block of memory if(mod(nvp,10000)==1), vertices_point(nvp+1:nvp+10001,1:3)=0; end % Add to vertices point list U V W vertices_point(nvp,1:3)=data; end case('vn') % A normal vector nvn=nvn+1; if(mod(nvn,10000)==1), vertices_normal(nvn+1:nvn+10001,1:3)=0; end % Add to vertices list I J K vertices_normal(nvn,1:3)=data; case('vt') % Vertices Texture Coordinate in photo % U V W nvt=nvt+1; if(length(data)==1) % Reserve block of memory if(mod(nvt,10000)==1), vertices_texture(nvt+1:nvt+10001,1)=0; end % Add to vertices texture list U vertices_texture(nvt,1)=data; elseif(length(data)==2) % Reserve block of memory if(mod(nvt,10000)==1), vertices_texture(nvt+1:nvt+10001,1:2)=0; end % Add to vertices texture list U V vertices_texture(nvt,1:2)=data; else % Reserve block of memory if(mod(nvt,10000)==1), vertices_texture(nvt+1:nvt+10001,1:3)=0; end % Add to vertices texture list U V W vertices_texture(nvt,1:3)=data; end case('l') no=no+1; %if(mod(no,10000)==1) % objects(no+10001).data=0; %end array_vertices=[]; array_texture=[]; for i=1:length(data), switch class(data) case 'cell' tvals=str2double(stringsplit(data{i},'/')); case 'string' tvals=str2double(stringsplit(data,'/')); otherwise tvals=data(i); end val=tvals(1); if(val<0), val=val+1+nv; end array_vertices(i)=val; if(length(tvals)>1), val=tvals(2); if(val<0), val=val+1+nvt; end array_texture(i)=val; end end objects(no).type='l'; objects(no).data.vertices=array_vertices; objects(no).data.texture=array_texture; case('f') no=no+1; %if(mod(no,10000)==1) % objects(no+10001).data=0; %end array_vertices=[]; array_texture=[]; array_normal=[]; for i=1:length(data); switch class(data) case 'cell' tvals=str2double(stringsplit(data{i},'/')); case 'string' tvals=str2double(stringsplit(data,'/')); otherwise tvals=data(i); end val=tvals(1); if(val<0), val=val+1+nv; end array_vertices(i)=val; if(length(tvals)>1), if(isfinite(tvals(2))) val=tvals(2); if(val<0), val=val+1+nvt; end array_texture(i)=val; end end if(length(tvals)>2), val=tvals(3); if(val<0), val=val+1+nvn; end array_normal(i)=val; end end % A face of more than 3 indices is always split into % multiple faces of only 3 indices. objects(no).type='f'; findex=1:min (3,length(array_vertices)); objects(no).data.vertices=array_vertices(findex); if(~isempty(array_texture)),objects(no).data.texture=array_texture(findex); end if(~isempty(array_normal)),objects(no).data.normal=array_normal(findex); end for i=1:length(array_vertices)-3; no=no+1; %if(mod(no,10000)==1), objects(no+10001).data=0; end findex=[1 2+i 3+i]; findex(findex>length(array_vertices))=findex(findex>length(array_vertices))-length(array_vertices); objects(no).type='f'; objects(no).data.vertices=array_vertices(findex); if(~isempty(array_texture)),objects(no).data.texture=array_texture(findex); end if(~isempty(array_normal)),objects(no).data.normal=array_normal(findex); end end case{'#','$'} % Comment tline=' %'; if(iscell(data)) for i=1:length(data), tline=[tline ' ' data{i}]; end else tline=[tline data]; end if(verbose), disp(tline); end case{''} otherwise no=no+1; %if(mod(no,10000)==1), objects(no+10001).data=0; end objects(no).type=type; objects(no).data=data; end end % Initialize new object list, which will contain the "collapsed" objects objects2(no).data=0; index=0; i=0; while (i<no), i=i+1; type=objects(i).type; % First face found if((length(type)==1)&&(type(1)=='f')) % Get number of faces for j=i:no type=objects(j).type; if((length(type)~=1)||(type(1)~='f')) j=j-1; break; end end numfaces=(j-i)+1; index=index+1; objects2(index).type='f'; % Process last face first to allocate memory objects2(index).data.vertices(numfaces,:)= objects(i).data.vertices; if(isfield(objects(i).data,'texture')) objects2(index).data.texture(numfaces,:) = objects(i).data.texture; else objects2(index).data.texture=[]; end if(isfield(objects(i).data,'normal')) objects2(index).data.normal(numfaces,:) = objects(i).data.normal; else objects2(index).data.normal=[]; end % All faces to arrays for k=1:numfaces objects2(index).data.vertices(k,:)= objects(i+k-1).data.vertices; if(isfield(objects(i).data,'texture')) objects2(index).data.texture(k,:) = objects(i+k-1).data.texture; end if(isfield(objects(i).data,'normal')) objects2(index).data.normal(k,:) = objects(i+k-1).data.normal; end end i=j; else index=index+1; objects2(index).type=objects(i).type; objects2(index).data=objects(i).data; end end % Add all data to output struct OBJ.objects=objects2(1:index); OBJ.material=material; OBJ.vertices=vertices(1:nv,:); OBJ.vertices_point=vertices_point(1:nvp,:); OBJ.vertices_normal=vertices_normal(1:nvn,:); OBJ.vertices_texture=vertices_texture(1:nvt,:); if(verbose),disp('Finished Reading Object file'); end function twords=stringsplit(tline,tchar) % Get start and end position of all "words" separated by a char i=find(tline(2:end-1)==tchar)+1; i_start=[1 i+1]; i_end=[i-1 length(tline)]; % Create a cell array of the words twords=cell(1,length(i_start)); for j=1:length(i_start), twords{j}=tline(i_start(j):i_end(j)); end function file_words=file2cellarray(filename) % Open a DI3D OBJ textfile fid=fopen(filename,'r'); file_text=fread(fid, inf, 'uint8=>char')'; fclose(fid); file_lines = regexp(file_text, '\n+', 'split'); file_words = regexp(file_lines, '\s+', 'split'); function [ftype fdata]=fixlines(file_words) ftype=cell(size(file_words)); fdata=cell(size(file_words)); iline=0; jline=0; while(iline<length(file_words)) iline=iline+1; twords=removeemptycells(file_words{iline}); if(~isempty(twords)) % Add next line to current line when line end with '\' while(strcmp(twords{end},'\')&&iline<length(file_words)) iline=iline+1; twords(end)=[]; twords=[twords removeemptycells(file_words{iline})]; end % Values to double type=twords{1}; stringdold=true; j=0; switch(type) case{'#','$'} for i=2:length(twords) j=j+1; twords{j}=twords{i}; end otherwise for i=2:length(twords) str=twords{i}; val=str2double(str); stringd=~isfinite(val); if(stringd) j=j+1; twords{j}=str; else if(stringdold) j=j+1; twords{j}=val; else twords{j}=[twords{j} val]; end end stringdold=stringd; end end twords(j+1:end)=[]; jline=jline+1; ftype{jline}=type; if(length(twords)==1), twords=twords{1}; end fdata{jline}=twords; end end ftype(jline+1:end)=[]; fdata(jline+1:end)=[]; function b=removeemptycells(a) j=0; b={}; for i=1:length(a); if(~isempty(a{i})),j=j+1; b{j}=a{i}; end; end function objects=readmtl(filename_mtl,verbose) if(verbose),disp(['Reading Material file : ' filename_mtl]); end file_words=file2cellarray(filename_mtl); % Remove empty cells, merge lines split by "\" and convert strings with values to double [ftype fdata]= fixlines(file_words); % Surface data objects.type(length(ftype))=0; objects.data(length(ftype))=0; no=0; % Loop through the Wavefront object file for iline=1:length(ftype) type=ftype{iline}; data=fdata{iline}; % Switch on data type line switch(type) case{'#','$'} % Comment tline=' %'; if(iscell(data)) for i=1:length(data), tline=[tline ' ' data{i}]; end else tline=[tline data]; end if(verbose), disp(tline); end case{''} otherwise no=no+1; if(mod(no,10000)==1), objects(no+10001).data=0; end objects(no).type=type; objects(no).data=data; end end objects=objects(1:no); if(verbose),disp('Finished Reading Material file'); end