mirror of
https://github.com/dpethes/rerogue.git
synced 2025-06-07 18:58:32 +02:00
model viewer: load models directly from DAT file
Displays a filelist with hob files, use it to select a mesh. Also, hob and hmt parsers now load data directly from memory streams.
This commit is contained in:
parent
a2b074d9e8
commit
1aff4888ab
@ -21,4 +21,5 @@ syntax: glob
|
|||||||
*.png
|
*.png
|
||||||
*.pnm
|
*.pnm
|
||||||
*.tga
|
*.tga
|
||||||
*.raw
|
*.raw
|
||||||
|
imgui.ini
|
@ -4,7 +4,7 @@ unit hob_mesh;
|
|||||||
interface
|
interface
|
||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, SysUtils, gl, GLext, math, gvector,
|
Classes, SysUtils, gl, GLext, math, gvector, imgui,
|
||||||
hob_parser, hmt_parser;
|
hob_parser, hmt_parser;
|
||||||
|
|
||||||
type
|
type
|
||||||
@ -50,12 +50,12 @@ type
|
|||||||
_materials: array of TMaterial;
|
_materials: array of TMaterial;
|
||||||
_hmt: THmtFile;
|
_hmt: THmtFile;
|
||||||
_hmt_loaded: boolean;
|
_hmt_loaded: boolean;
|
||||||
procedure HmtRead(const filename: string);
|
procedure HmtRead(stream: TMemoryStream);
|
||||||
procedure HobRead(const filename: string);
|
procedure HobRead(stream: TMemoryStream);
|
||||||
procedure HobReadMesh(const mesh: THobObject);
|
procedure HobReadMesh(const mesh: THobObject);
|
||||||
public
|
public
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
procedure Load(const hob_filename, hmt_filename: string);
|
procedure Load(hob, hmt: TMemoryStream);
|
||||||
procedure InitGL;
|
procedure InitGL;
|
||||||
procedure DrawGL(opts: TRenderOpts);
|
procedure DrawGL(opts: TRenderOpts);
|
||||||
procedure ExportObj(const obj_name: string);
|
procedure ExportObj(const obj_name: string);
|
||||||
@ -150,12 +150,12 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TModel.HobRead(const filename: string);
|
procedure TModel.HobRead(stream: TMemoryStream);
|
||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
hob: THobFile;
|
hob: THobFile;
|
||||||
begin
|
begin
|
||||||
hob := ParseHobFile(filename);
|
hob := ParseHobFile(stream);
|
||||||
for i := 0 to 0 do
|
for i := 0 to 0 do
|
||||||
HobReadMesh(hob.objects[i]);
|
HobReadMesh(hob.objects[i]);
|
||||||
WriteLn('vertices: ', _vertices.Size);
|
WriteLn('vertices: ', _vertices.Size);
|
||||||
@ -163,7 +163,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TModel.HmtRead(const filename: string);
|
procedure TModel.HmtRead(stream: TMemoryStream);
|
||||||
procedure SetTexByName (var mat: TMaterial; const name: string);
|
procedure SetTexByName (var mat: TMaterial; const name: string);
|
||||||
var
|
var
|
||||||
i: integer;
|
i: integer;
|
||||||
@ -192,7 +192,7 @@ procedure TModel.HmtRead(const filename: string);
|
|||||||
var
|
var
|
||||||
i: integer;
|
i: integer;
|
||||||
begin
|
begin
|
||||||
_hmt := ParseHmtFile(filename);
|
_hmt := ParseHmtFile(stream);
|
||||||
SetLength(_materials, _hmt.material_count);
|
SetLength(_materials, _hmt.material_count);
|
||||||
for i := 0 to _hmt.material_count - 1 do
|
for i := 0 to _hmt.material_count - 1 do
|
||||||
SetTexByName(_materials[i], _hmt.materials[i].name_string);
|
SetTexByName(_materials[i], _hmt.materials[i].name_string);
|
||||||
@ -205,19 +205,15 @@ begin
|
|||||||
// _triangles.Free;
|
// _triangles.Free;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TModel.Load(const hob_filename, hmt_filename: string);
|
procedure TModel.Load(hob, hmt: TMemoryStream);
|
||||||
begin
|
begin
|
||||||
_vertices := TVertexList.Create;
|
_vertices := TVertexList.Create;
|
||||||
//_triangles := TTriangleList.Create;
|
//_triangles := TTriangleList.Create;
|
||||||
WriteLn('Loading mesh file ', hob_filename);
|
WriteLn('Loading mesh file');
|
||||||
HobRead(hob_filename);
|
HobRead(hob);
|
||||||
if FileExists(hmt_filename) then begin
|
WriteLn('Loading material file');
|
||||||
WriteLn('Loading material file ', hmt_filename);
|
HmtRead(hmt);
|
||||||
HmtRead(hmt_filename);
|
_hmt_loaded := true;
|
||||||
_hmt_loaded := true;
|
|
||||||
end else begin
|
|
||||||
_hmt_loaded := false;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure pnm_save(const fname: string; const p: pbyte; const w, h: integer);
|
procedure pnm_save(const fname: string; const p: pbyte; const w, h: integer);
|
||||||
@ -286,6 +282,7 @@ procedure TModel.DrawGL(opts: TRenderOpts);
|
|||||||
var
|
var
|
||||||
vert: TVertex;
|
vert: TVertex;
|
||||||
i, k: integer;
|
i, k: integer;
|
||||||
|
triangle_count: integer = 0;
|
||||||
|
|
||||||
procedure DrawTri(tri: TTriangle);
|
procedure DrawTri(tri: TTriangle);
|
||||||
var
|
var
|
||||||
@ -309,13 +306,12 @@ var
|
|||||||
glVertex3fv(@tri.vertices[k]);
|
glVertex3fv(@tri.vertices[k]);
|
||||||
end;
|
end;
|
||||||
glEnd;
|
glEnd;
|
||||||
|
triangle_count += 1;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
if opts.wireframe then
|
if opts.wireframe then
|
||||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
||||||
else
|
|
||||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
||||||
|
|
||||||
glDisable(GL_TEXTURE_2D);
|
glDisable(GL_TEXTURE_2D);
|
||||||
if opts.points then begin
|
if opts.points then begin
|
||||||
@ -333,6 +329,13 @@ begin
|
|||||||
//k := min(opts.fg_to_draw, Length(_triangles) - 1);
|
//k := min(opts.fg_to_draw, Length(_triangles) - 1);
|
||||||
for i := 0 to _triangles[k].Size - 1 do
|
for i := 0 to _triangles[k].Size - 1 do
|
||||||
DrawTri(_triangles[k][i]);
|
DrawTri(_triangles[k][i]);
|
||||||
|
|
||||||
|
if opts.wireframe then
|
||||||
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||||
|
|
||||||
|
igBegin('Mesh');
|
||||||
|
ImguiText(Format('triangles: %d (vertices: %d)', [triangle_count, _vertices.Size]));
|
||||||
|
igEnd;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ type
|
|||||||
PImGuiContext = Pointer;
|
PImGuiContext = Pointer;
|
||||||
PImGuiSizeConstraintCallbackData = ^ImGuiSizeConstraintCallbackData;
|
PImGuiSizeConstraintCallbackData = ^ImGuiSizeConstraintCallbackData;
|
||||||
PImGuiStorage = ^ImGuiStorage;
|
PImGuiStorage = ^ImGuiStorage;
|
||||||
PImGuiStyle = ^ImGuiStyle;
|
|
||||||
PImGuiTextEditCallbackData = ^ImGuiTextEditCallbackData;
|
PImGuiTextEditCallbackData = ^ImGuiTextEditCallbackData;
|
||||||
|
|
||||||
ImVec2 = record
|
ImVec2 = record
|
||||||
@ -98,7 +97,83 @@ type
|
|||||||
//ImGuiKey_COUNT // this is unnecessary, if we declare KeyMap as array[ImGuiKey_]
|
//ImGuiKey_COUNT // this is unnecessary, if we declare KeyMap as array[ImGuiKey_]
|
||||||
);
|
);
|
||||||
|
|
||||||
//structs
|
ImGuiCol_ = (
|
||||||
|
ImGuiCol_Text,
|
||||||
|
ImGuiCol_TextDisabled,
|
||||||
|
ImGuiCol_WindowBg,
|
||||||
|
ImGuiCol_ChildWindowBg,
|
||||||
|
ImGuiCol_PopupBg,
|
||||||
|
ImGuiCol_Border,
|
||||||
|
ImGuiCol_BorderShadow,
|
||||||
|
ImGuiCol_FrameBg,
|
||||||
|
ImGuiCol_FrameBgHovered,
|
||||||
|
ImGuiCol_FrameBgActive,
|
||||||
|
ImGuiCol_TitleBg,
|
||||||
|
ImGuiCol_TitleBgCollapsed,
|
||||||
|
ImGuiCol_TitleBgActive,
|
||||||
|
ImGuiCol_MenuBarBg,
|
||||||
|
ImGuiCol_ScrollbarBg,
|
||||||
|
ImGuiCol_ScrollbarGrab,
|
||||||
|
ImGuiCol_ScrollbarGrabHovered,
|
||||||
|
ImGuiCol_ScrollbarGrabActive,
|
||||||
|
ImGuiCol_ComboBg,
|
||||||
|
ImGuiCol_CheckMark,
|
||||||
|
ImGuiCol_SliderGrab,
|
||||||
|
ImGuiCol_SliderGrabActive,
|
||||||
|
ImGuiCol_Button,
|
||||||
|
ImGuiCol_ButtonHovered,
|
||||||
|
ImGuiCol_ButtonActive,
|
||||||
|
ImGuiCol_Header,
|
||||||
|
ImGuiCol_HeaderHovered,
|
||||||
|
ImGuiCol_HeaderActive,
|
||||||
|
ImGuiCol_Column,
|
||||||
|
ImGuiCol_ColumnHovered,
|
||||||
|
ImGuiCol_ColumnActive,
|
||||||
|
ImGuiCol_ResizeGrip,
|
||||||
|
ImGuiCol_ResizeGripHovered,
|
||||||
|
ImGuiCol_ResizeGripActive,
|
||||||
|
ImGuiCol_CloseButton,
|
||||||
|
ImGuiCol_CloseButtonHovered,
|
||||||
|
ImGuiCol_CloseButtonActive,
|
||||||
|
ImGuiCol_PlotLines,
|
||||||
|
ImGuiCol_PlotLinesHovered,
|
||||||
|
ImGuiCol_PlotHistogram,
|
||||||
|
ImGuiCol_PlotHistogramHovered,
|
||||||
|
ImGuiCol_TextSelectedBg,
|
||||||
|
ImGuiCol_ModalWindowDarkening
|
||||||
|
//ImGuiCol_COUNT - unnecessary
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
{ structs }
|
||||||
|
ImGuiStyle = record
|
||||||
|
Alpha : single;
|
||||||
|
WindowPadding : ImVec2;
|
||||||
|
WindowMinSize : ImVec2;
|
||||||
|
WindowRounding : single;
|
||||||
|
WindowTitleAlign : ImVec2;
|
||||||
|
ChildWindowRounding : single;
|
||||||
|
FramePadding : ImVec2;
|
||||||
|
FrameRounding : single;
|
||||||
|
ItemSpacing : ImVec2;
|
||||||
|
ItemInnerSpacing : ImVec2;
|
||||||
|
TouchExtraPadding : ImVec2;
|
||||||
|
IndentSpacing : single;
|
||||||
|
ColumnsMinSpacing : single;
|
||||||
|
ScrollbarSize : single;
|
||||||
|
ScrollbarRounding : single;
|
||||||
|
GrabMinSize : single;
|
||||||
|
GrabRounding : single;
|
||||||
|
ButtonTextAlign : ImVec2;
|
||||||
|
DisplayWindowPadding : ImVec2;
|
||||||
|
DisplaySafeAreaPadding : ImVec2;
|
||||||
|
AntiAliasedLines : bool;
|
||||||
|
AntiAliasedShapes : bool;
|
||||||
|
CurveTessellationTol : single;
|
||||||
|
Colors: array[ImGuiCol_] of ImVec4;
|
||||||
|
end;
|
||||||
|
PImGuiStyle = ^ImGuiStyle;
|
||||||
|
|
||||||
ImGuiIO = record
|
ImGuiIO = record
|
||||||
DisplaySize : ImVec2;
|
DisplaySize : ImVec2;
|
||||||
DeltaTime : single;
|
DeltaTime : single;
|
||||||
@ -194,8 +269,6 @@ type
|
|||||||
TotalIdxCount: integer;
|
TotalIdxCount: integer;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
ImGuiStyle = record
|
|
||||||
end;
|
|
||||||
ImGuiTextEditCallbackData = record
|
ImGuiTextEditCallbackData = record
|
||||||
end;
|
end;
|
||||||
ImGuiSizeConstraintCallbackData = record
|
ImGuiSizeConstraintCallbackData = record
|
||||||
@ -702,6 +775,9 @@ procedure ImDrawList_UpdateTextureID(list: PImDrawList); cdecl; external ImguiLi
|
|||||||
//binding helpers
|
//binding helpers
|
||||||
function ImVec2Init(const x, y: single): Imvec2; inline;
|
function ImVec2Init(const x, y: single): Imvec2; inline;
|
||||||
|
|
||||||
|
procedure ImguiText(const s: string); inline;
|
||||||
|
function ImguiSelectable(const s: string; const selected: boolean): boolean; inline;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
function ImVec2Init(const x, y: single): Imvec2;
|
function ImVec2Init(const x, y: single): Imvec2;
|
||||||
@ -710,4 +786,14 @@ begin
|
|||||||
result.y := y;
|
result.y := y;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure ImguiText(const s: string);
|
||||||
|
begin
|
||||||
|
igText(pchar(s));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function ImguiSelectable(const s: string; const selected: boolean): boolean;
|
||||||
|
begin
|
||||||
|
result := igSelectable(pchar(s), selected, 0, ImVec2Init(0,0));
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
<PackageName Value="LCL"/>
|
<PackageName Value="LCL"/>
|
||||||
</Item1>
|
</Item1>
|
||||||
</RequiredPackages>
|
</RequiredPackages>
|
||||||
<Units Count="8">
|
<Units Count="9">
|
||||||
<Unit0>
|
<Unit0>
|
||||||
<Filename Value="model_viewer.pas"/>
|
<Filename Value="model_viewer.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
@ -67,6 +67,10 @@
|
|||||||
<Filename Value="imgui\imgui_impl_sdlgl2.pas"/>
|
<Filename Value="imgui\imgui_impl_sdlgl2.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
</Unit7>
|
</Unit7>
|
||||||
|
<Unit8>
|
||||||
|
<Filename Value="..\rs_units\rs_dat.pas"/>
|
||||||
|
<IsPartOfProject Value="True"/>
|
||||||
|
</Unit8>
|
||||||
</Units>
|
</Units>
|
||||||
</ProjectOptions>
|
</ProjectOptions>
|
||||||
<CompilerOptions>
|
<CompilerOptions>
|
||||||
|
@ -19,9 +19,9 @@ along with RS model viewer. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
program model_viewer;
|
program model_viewer;
|
||||||
|
|
||||||
uses
|
uses
|
||||||
sysutils, math,
|
sysutils, classes, math, strutils, gvector,
|
||||||
gl, glu, glext, sdl2, imgui, imgui_impl_sdlgl2,
|
gl, glu, glext, sdl2, imgui, imgui_impl_sdlgl2,
|
||||||
hob_mesh;
|
rs_dat, hob_mesh;
|
||||||
|
|
||||||
const
|
const
|
||||||
SCR_W_fscrn = 1024;
|
SCR_W_fscrn = 1024;
|
||||||
@ -35,11 +35,23 @@ const
|
|||||||
PitchIncrement = 0.5;
|
PitchIncrement = 0.5;
|
||||||
MouseTranslateMultiply = 0.025;
|
MouseTranslateMultiply = 0.025;
|
||||||
|
|
||||||
|
type
|
||||||
|
TFileListItem = record
|
||||||
|
name: string;
|
||||||
|
node_hob: PRsDatFileNode;
|
||||||
|
node_hmt: PRsDatFileNode;
|
||||||
|
end;
|
||||||
|
TFileList = specialize TVector<TFileListItem>;
|
||||||
|
|
||||||
var
|
var
|
||||||
g_window: PSDL_Window;
|
g_window: PSDL_Window;
|
||||||
g_ogl_context: TSDL_GLContext;
|
g_ogl_context: TSDL_GLContext;
|
||||||
|
|
||||||
model: TModel;
|
g_rsdata: TRSDatFile;
|
||||||
|
g_filelist: TFileList;
|
||||||
|
g_model: TModel;
|
||||||
|
g_model_name: string;
|
||||||
|
g_model_loading_failed: Boolean;
|
||||||
|
|
||||||
view: record
|
view: record
|
||||||
rotation_angle: single;
|
rotation_angle: single;
|
||||||
@ -132,9 +144,10 @@ begin
|
|||||||
if view.rotation_angle > 360 then
|
if view.rotation_angle > 360 then
|
||||||
view.rotation_angle -= 360;
|
view.rotation_angle -= 360;
|
||||||
|
|
||||||
model.DrawGL(view.opts);
|
if g_model <> nil then begin
|
||||||
|
g_model.DrawGL(view.opts);
|
||||||
|
end;
|
||||||
|
|
||||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
||||||
igRender;
|
igRender;
|
||||||
|
|
||||||
SDL_GL_SwapWindow(g_window);
|
SDL_GL_SwapWindow(g_window);
|
||||||
@ -272,7 +285,7 @@ begin
|
|||||||
view.autorotate := not view.autorotate;
|
view.autorotate := not view.autorotate;
|
||||||
key_pressed.autorotate := true;
|
key_pressed.autorotate := true;
|
||||||
end;
|
end;
|
||||||
//model rendering opts
|
//g_model rendering opts
|
||||||
SDLK_w:
|
SDLK_w:
|
||||||
if not key_pressed.wireframe then begin
|
if not key_pressed.wireframe then begin
|
||||||
view.opts.wireframe := not view.opts.wireframe;
|
view.opts.wireframe := not view.opts.wireframe;
|
||||||
@ -362,22 +375,137 @@ begin
|
|||||||
end; {case}
|
end; {case}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure LoadMesh(item: TFileListItem);
|
||||||
|
var
|
||||||
|
hob, hmt: TMemoryStream;
|
||||||
|
begin
|
||||||
|
if g_model <> nil then
|
||||||
|
g_model.Free;
|
||||||
|
hob := TMemoryStream.Create;
|
||||||
|
hob.WriteBuffer(item.node_hob^.Data^, item.node_hob^.size);
|
||||||
|
hob.Seek(0, soBeginning);
|
||||||
|
|
||||||
|
hmt := TMemoryStream.Create;
|
||||||
|
hmt.WriteBuffer(item.node_hmt^.Data^, item.node_hmt^.size);
|
||||||
|
hmt.Seek(0, soBeginning);
|
||||||
|
|
||||||
|
g_model := TModel.Create;
|
||||||
|
g_model.Load(hob, hmt);
|
||||||
|
g_model.InitGL;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure DrawGui;
|
||||||
|
var
|
||||||
|
showtest: bool;
|
||||||
|
style: PImGuiStyle;
|
||||||
|
file_item: TFileListItem;
|
||||||
|
fitem_selected: Boolean = false;
|
||||||
|
selected_mesh_name: String;
|
||||||
|
selected_item: TFileListItem;
|
||||||
|
begin
|
||||||
|
ImGui_ImplSdlGL2_NewFrame(g_window);
|
||||||
|
|
||||||
|
style := igGetStyle();
|
||||||
|
style^.WindowRounding := 0;
|
||||||
|
|
||||||
|
igBegin('Mesh');
|
||||||
|
if not g_model_loading_failed then begin
|
||||||
|
ImguiText(g_model_name);
|
||||||
|
end else
|
||||||
|
ImguiText('mesh loading failed :(');
|
||||||
|
igEnd;
|
||||||
|
|
||||||
|
igBegin('Rendering options');
|
||||||
|
igCheckbox('points', @view.opts.points);
|
||||||
|
igCheckbox('wireframe', @view.opts.wireframe);
|
||||||
|
igCheckbox('textures', @view.opts.textures);
|
||||||
|
igCheckbox('vertex colors', @view.opts.vcolors);
|
||||||
|
if g_model <> nil then
|
||||||
|
if igButton('Export to obj', ImVec2Init(0,0)) then
|
||||||
|
g_model.ExportObj('rs_exported.obj');
|
||||||
|
igEnd;
|
||||||
|
|
||||||
|
selected_mesh_name := EmptyStr;
|
||||||
|
igBegin('File list');
|
||||||
|
//todo filter
|
||||||
|
for file_item in g_filelist do begin
|
||||||
|
fitem_selected := file_item.name = g_model_name;
|
||||||
|
if ImguiSelectable(file_item.name, fitem_selected) then begin
|
||||||
|
selected_mesh_name := file_item.name;
|
||||||
|
selected_item := file_item;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
igEnd;
|
||||||
|
|
||||||
|
//igShowTestWindow(@showtest);
|
||||||
|
|
||||||
|
if (selected_mesh_name <> EmptyStr) and (selected_mesh_name <> g_model_name) then begin
|
||||||
|
try
|
||||||
|
LoadMesh(selected_item);
|
||||||
|
g_model_name := selected_mesh_name;
|
||||||
|
g_model_loading_failed := false;
|
||||||
|
except
|
||||||
|
g_model_loading_failed := true;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
//we only care about HOB and HMT files
|
||||||
|
procedure LoadMeshFilelist;
|
||||||
|
procedure AddFile(const path: string; const fnode, fnode_next: PRsDatFileNode);
|
||||||
|
var
|
||||||
|
item: TFileListItem;
|
||||||
|
name: String;
|
||||||
|
i: integer;
|
||||||
|
begin
|
||||||
|
if fnode^.is_directory then begin
|
||||||
|
for i := 0 to Length(fnode^.nodes)-2 do //hob/hmt always go in pairs
|
||||||
|
AddFile(path + fnode^.Name + '/', fnode^.nodes[i], fnode^.nodes[i+1]);
|
||||||
|
end
|
||||||
|
else begin
|
||||||
|
name := fnode^.name;
|
||||||
|
if AnsiEndsText('hob', name) then begin
|
||||||
|
Assert(AnsiEndsText('hmt', fnode_next^.name), 'no HMT file!');
|
||||||
|
item.name := path + name;
|
||||||
|
item.node_hob := fnode;
|
||||||
|
item.node_hmt := fnode_next;
|
||||||
|
g_filelist.PushBack(item);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
var
|
||||||
|
rs_files: TRsDatFileNodeList;
|
||||||
|
file_: TRsDatFileNode;
|
||||||
|
begin
|
||||||
|
rs_files := g_rsdata.GetStructure();
|
||||||
|
for file_ in rs_files do begin
|
||||||
|
AddFile('', @file_, nil);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
//******************************************************************************
|
//******************************************************************************
|
||||||
var
|
var
|
||||||
sec, frames: integer;
|
sec, frames: integer;
|
||||||
event: TSDL_Event;
|
event: TSDL_Event;
|
||||||
done: boolean;
|
done: boolean;
|
||||||
hob_file, hmt_file, obj_file: string;
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
if Paramcount < 1 then begin
|
if not (FileExists(RS_DATA_HDR) and FileExists(RS_DATA_DAT)) then begin
|
||||||
writeln('specify HOB file');
|
writeln('RS data files not found!');
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
hob_file := ParamStr(1);
|
|
||||||
hmt_file := StringReplace(hob_file, '.hob', '.hmt', [rfIgnoreCase]);
|
writeln('loading data');
|
||||||
model := TModel.Create;
|
g_rsdata := TRSDatFile.Create(RS_DATA_HDR, RS_DATA_DAT);
|
||||||
model.Load(hob_file, hmt_file);
|
g_rsdata.Parse();
|
||||||
|
g_filelist := TFileList.Create;
|
||||||
|
LoadMeshFilelist();
|
||||||
|
|
||||||
|
g_model := nil;
|
||||||
|
g_model_name := '';
|
||||||
|
g_model_loading_failed := false;
|
||||||
|
|
||||||
writeln('Init SDL...');
|
writeln('Init SDL...');
|
||||||
SDL_Init(SDL_INIT_VIDEO or SDL_INIT_TIMER);
|
SDL_Init(SDL_INIT_VIDEO or SDL_INIT_TIMER);
|
||||||
@ -387,11 +515,6 @@ begin
|
|||||||
SetGLWindowSize(g_window^.w, g_window^.h);
|
SetGLWindowSize(g_window^.w, g_window^.h);
|
||||||
|
|
||||||
InitView;
|
InitView;
|
||||||
model.InitGL;
|
|
||||||
|
|
||||||
//export
|
|
||||||
//obj_file := StringReplace(hob_file, '.hob', '.obj', [rfIgnoreCase]);
|
|
||||||
//model.ExportObj(obj_file);
|
|
||||||
|
|
||||||
sec := SDL_GetTicks;
|
sec := SDL_GetTicks;
|
||||||
frames := 0;
|
frames := 0;
|
||||||
@ -399,15 +522,8 @@ begin
|
|||||||
key_pressed.wireframe := false;
|
key_pressed.wireframe := false;
|
||||||
key_pressed.fullscreen := false;
|
key_pressed.fullscreen := false;
|
||||||
while not Done do begin
|
while not Done do begin
|
||||||
ImGui_ImplSdlGL2_NewFrame(g_window);
|
|
||||||
|
|
||||||
igBegin('rendering options');
|
|
||||||
igCheckbox('points', @view.opts.points);
|
|
||||||
igCheckbox('wireframe', @view.opts.wireframe);
|
|
||||||
igCheckbox('textures', @view.opts.textures);
|
|
||||||
igCheckbox('vertex colors', @view.opts.vcolors);
|
|
||||||
igEnd;
|
|
||||||
|
|
||||||
|
DrawGui;
|
||||||
DrawGLScene;
|
DrawGLScene;
|
||||||
|
|
||||||
while SDL_PollEvent(@event) > 0 do
|
while SDL_PollEvent(@event) > 0 do
|
||||||
@ -419,13 +535,16 @@ begin
|
|||||||
frames := 0;
|
frames := 0;
|
||||||
sec := SDL_GetTicks;
|
sec := SDL_GetTicks;
|
||||||
end;
|
end;
|
||||||
SDL_Delay(10);
|
SDL_Delay(5);
|
||||||
//WindowScreenshot( surface^.w, surface^.h );
|
//WindowScreenshot( surface^.w, surface^.h );
|
||||||
end;
|
end;
|
||||||
|
|
||||||
model.Free;
|
|
||||||
|
|
||||||
WindowFree;
|
WindowFree;
|
||||||
SDL_Quit;
|
SDL_Quit;
|
||||||
|
|
||||||
|
if g_model <> nil then
|
||||||
|
g_model.Free;
|
||||||
|
g_filelist.Free;
|
||||||
|
g_rsdata.Free;
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ type
|
|||||||
textures: array of THmtTexture;
|
textures: array of THmtTexture;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function ParseHmtFile(const fname: string): THmtFile;
|
function ParseHmtFile(f: TMemoryStream): THmtFile;
|
||||||
|
|
||||||
//**************************************************************************************************
|
//**************************************************************************************************
|
||||||
implementation
|
implementation
|
||||||
@ -138,15 +138,11 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
function ParseHmtFile(const fname: string): THmtFile;
|
function ParseHmtFile(f: TMemoryStream): THmtFile;
|
||||||
var
|
var
|
||||||
f: TMemoryStream;
|
|
||||||
hmt: THmtFile;
|
hmt: THmtFile;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
f := TMemoryStream.Create;
|
|
||||||
f.LoadFromFile(fname);
|
|
||||||
|
|
||||||
//read main info
|
//read main info
|
||||||
hmt.material_count := f.ReadDWord;
|
hmt.material_count := f.ReadDWord;
|
||||||
hmt.texture_offset := f.ReadDWord;
|
hmt.texture_offset := f.ReadDWord;
|
||||||
@ -175,7 +171,6 @@ begin
|
|||||||
ReadTexture(hmt.textures[i], f);
|
ReadTexture(hmt.textures[i], f);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
f.Free;
|
|
||||||
result := hmt;
|
result := hmt;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ type
|
|||||||
objects: array of THobObject;
|
objects: array of THobObject;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function ParseHobFile(const fname: string): THobFile;
|
function ParseHobFile(f: TMemoryStream): THobFile;
|
||||||
|
|
||||||
//**************************************************************************************************
|
//**************************************************************************************************
|
||||||
implementation
|
implementation
|
||||||
@ -323,16 +323,12 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
function ParseHobFile(const fname: string): THobFile;
|
function ParseHobFile(f: TMemoryStream): THobFile;
|
||||||
var
|
var
|
||||||
f: TMemoryStream;
|
|
||||||
hob: THobFile;
|
hob: THobFile;
|
||||||
i: integer;
|
i: integer;
|
||||||
filepos: int64;
|
filepos: int64;
|
||||||
begin
|
begin
|
||||||
f := TMemoryStream.Create;
|
|
||||||
f.LoadFromFile(fname);
|
|
||||||
|
|
||||||
hob.obj_count := f.ReadDWord;
|
hob.obj_count := f.ReadDWord;
|
||||||
f.ReadDWord; //sometimes face block start, but useless in general
|
f.ReadDWord; //sometimes face block start, but useless in general
|
||||||
|
|
||||||
@ -353,7 +349,6 @@ begin
|
|||||||
f.Seek(filepos + 116, fsFromBeginning);
|
f.Seek(filepos + 116, fsFromBeginning);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
f.Free;
|
|
||||||
result := hob;
|
result := hob;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
304
rs_units/rs_dat.pas
Normal file
304
rs_units/rs_dat.pas
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
{
|
||||||
|
Loads DATA.DAT/HDR files to memory and parses the file structure.
|
||||||
|
RS most likely loads just the file structure and the actual file data gets loaded as needed.
|
||||||
|
We have plenty of ram nowadays, so read files just once and then load from memory.
|
||||||
|
}
|
||||||
|
unit rs_dat;
|
||||||
|
{$mode objfpc}{$H+}
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
Classes, SysUtils, gvector;
|
||||||
|
|
||||||
|
const
|
||||||
|
RS_DATA_HDR = 'DATA.HDR';
|
||||||
|
RS_DATA_DAT = 'DATA.DAT';
|
||||||
|
RS_DATA_FEDirectoryFlag = %10000000;
|
||||||
|
|
||||||
|
type
|
||||||
|
//data file entry
|
||||||
|
TRsDatFileEntry = packed record
|
||||||
|
offset: longword;
|
||||||
|
length: longword;
|
||||||
|
padding: longword;
|
||||||
|
type_flag: word;
|
||||||
|
sub_entry_size: word;
|
||||||
|
filename: array[0..15] of char;
|
||||||
|
end;
|
||||||
|
PRsDatFileEntry = ^TRsDatFileEntry;
|
||||||
|
|
||||||
|
//file or directory node
|
||||||
|
PRsDatFileNode = ^TRsDatFileNode;
|
||||||
|
TRsDatFileNode = record
|
||||||
|
Name: string;
|
||||||
|
is_directory: boolean;
|
||||||
|
subentries_count: integer;
|
||||||
|
offset: longword;
|
||||||
|
size: longword;
|
||||||
|
entry: TRsDatFileEntry;
|
||||||
|
Data: pbyte;
|
||||||
|
nodes: array of PRsDatFileNode; //children if directory
|
||||||
|
end;
|
||||||
|
|
||||||
|
//header file entry
|
||||||
|
TRsHdrSection = record
|
||||||
|
Name: string; //section name
|
||||||
|
dat_offset: integer; //offset in dat file
|
||||||
|
Data: pbyte; //section's data pointer
|
||||||
|
nodes: array of TRsDatFileNode; //all file entries / nodes
|
||||||
|
root: TRsDatFileNode; //tree structure of nodes
|
||||||
|
end;
|
||||||
|
TRsDatFileNodeList = specialize TVector<TRsDatFileNode>;
|
||||||
|
|
||||||
|
{ TRSDatFile }
|
||||||
|
|
||||||
|
TRSDatFile = class
|
||||||
|
public
|
||||||
|
constructor Create(const hdr_file, dat_file: string); //loads header & data files to memory
|
||||||
|
destructor Destroy; override; //cleanup loaded memory
|
||||||
|
procedure Parse(); //parse file structure
|
||||||
|
function GetStructure(): TRsDatFileNodeList; //list of sections, list must be freed by user
|
||||||
|
procedure WriteFilesToDirectory(const path: string);
|
||||||
|
private
|
||||||
|
m_sections: array of TRsHdrSection;
|
||||||
|
m_data: pbyte;
|
||||||
|
|
||||||
|
procedure ReadHeaderFile(const fname: string);
|
||||||
|
procedure ReadDatFile(const fname: string);
|
||||||
|
function ReadEntry(const stream: TMemoryStream; const mem_data: pbyte): TRsDatFileNode;
|
||||||
|
procedure ReadSectionEntries(var section: TRsHdrSection; const mem_data: pbyte);
|
||||||
|
procedure ParseSectionStructure(var section: TRsHdrSection);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function CountSubNodes(node: PRsDatFileNode): integer;
|
||||||
|
function CountSubNodeSizes(node: PRsDatFileNode): integer;
|
||||||
|
|
||||||
|
|
||||||
|
//**************************************************************************************************
|
||||||
|
implementation
|
||||||
|
|
||||||
|
function CountSubNodes(node: PRsDatFileNode): integer;
|
||||||
|
var
|
||||||
|
i: integer;
|
||||||
|
begin
|
||||||
|
Result := 1;
|
||||||
|
if node^.is_directory then
|
||||||
|
for i := 0 to Length(node^.nodes) - 1 do
|
||||||
|
Result += CountSubNodes(node^.nodes[i]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function CountSubNodeSizes(node: PRsDatFileNode): integer;
|
||||||
|
var
|
||||||
|
i: integer;
|
||||||
|
begin
|
||||||
|
Result := node^.size;
|
||||||
|
if node^.is_directory then
|
||||||
|
for i := 0 to Length(node^.nodes) - 1 do
|
||||||
|
Result += CountSubNodeSizes(node^.nodes[i]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure SaveFile(const name: string; const buffer: pbyte; const buf_size: integer);
|
||||||
|
var
|
||||||
|
f: file;
|
||||||
|
fname: string;
|
||||||
|
begin
|
||||||
|
fname := name;
|
||||||
|
AssignFile(f, fname);
|
||||||
|
Rewrite(f, 1);
|
||||||
|
BlockWrite(f, buffer^, buf_size);
|
||||||
|
CloseFile(f);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TRSDatFile }
|
||||||
|
|
||||||
|
constructor TRSDatFile.Create(const hdr_file, dat_file: string);
|
||||||
|
begin
|
||||||
|
ReadHeaderFile(hdr_file);
|
||||||
|
ReadDatFile(dat_file);
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TRSDatFile.Destroy;
|
||||||
|
begin
|
||||||
|
inherited Destroy;
|
||||||
|
m_sections := nil;
|
||||||
|
freemem(m_data);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRSDatFile.Parse;
|
||||||
|
var
|
||||||
|
i: integer;
|
||||||
|
begin
|
||||||
|
for i := 0 to length(m_sections) - 1 do begin
|
||||||
|
Writeln('reading section ', m_sections[i].name);
|
||||||
|
ReadSectionEntries(m_sections[i], m_data + m_sections[i].dat_offset);
|
||||||
|
ParseSectionStructure(m_sections[i]);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TRSDatFile.GetStructure: TRsDatFileNodeList;
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
Assert(Length(m_sections) > 0, 'data not parsed');
|
||||||
|
result := TRsDatFileNodeList.Create;
|
||||||
|
for i := 0 to length(m_sections) - 1 do
|
||||||
|
result.PushBack(m_sections[i].root);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRSDatFile.ReadHeaderFile(const fname: string);
|
||||||
|
var
|
||||||
|
f: file;
|
||||||
|
section: TRsHdrSection;
|
||||||
|
section_n: integer;
|
||||||
|
i: integer;
|
||||||
|
buffer: array[0..15] of char;
|
||||||
|
begin
|
||||||
|
assignfile(f, fname);
|
||||||
|
reset(f, 1);
|
||||||
|
section_n := FileSize(f) div 32;
|
||||||
|
SetLength(m_sections, section_n);
|
||||||
|
|
||||||
|
for i := 0 to section_n - 1 do begin
|
||||||
|
blockread(f, buffer, 16); //name
|
||||||
|
section.name := trim(buffer);
|
||||||
|
blockread(f, buffer, 12); //empty
|
||||||
|
blockread(f, section.dat_offset, 4); //offset in m_data.DAT
|
||||||
|
m_sections[i] := section;
|
||||||
|
end;
|
||||||
|
|
||||||
|
closefile(f);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRSDatFile.ReadDatFile(const fname: string);
|
||||||
|
var
|
||||||
|
f: file;
|
||||||
|
fsize: integer;
|
||||||
|
begin
|
||||||
|
AssignFile(f, fname);
|
||||||
|
reset(f, 1);
|
||||||
|
fsize := FileSize(f);
|
||||||
|
m_data := getmem(fsize);
|
||||||
|
Blockread(f, m_data^, fsize);
|
||||||
|
closefile(f);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TRSDatFile.ReadEntry(const stream: TMemoryStream; const mem_data: pbyte): TRsDatFileNode;
|
||||||
|
var
|
||||||
|
entry: TRsDatFileEntry;
|
||||||
|
begin
|
||||||
|
stream.ReadBuffer(entry, 32);
|
||||||
|
result.name := Trim(entry.filename);
|
||||||
|
result.offset := entry.offset;
|
||||||
|
result.size := entry.length;
|
||||||
|
result.is_directory := (entry.type_flag and RS_DATA_FEDirectoryFlag) <> 0;
|
||||||
|
result.subentries_count := entry.sub_entry_size div 32 - 1;
|
||||||
|
result.entry := entry;
|
||||||
|
result.data := nil;
|
||||||
|
if not result.is_directory then
|
||||||
|
result.Data := mem_data + entry.offset;
|
||||||
|
|
||||||
|
//if (result.offset mod 32) <> 0 then writeln('unaligned offset');
|
||||||
|
writeln(stderr, format('name: %s size: %d dir: %s subsize: %d flags: %s',
|
||||||
|
[result.Name, entry.length, BoolToStr(result.is_directory),
|
||||||
|
entry.sub_entry_size, binStr(entry.type_flag, 16)]));
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRSDatFile.ReadSectionEntries(var section: TRsHdrSection; const mem_data: pbyte);
|
||||||
|
var
|
||||||
|
entries_offset: integer;
|
||||||
|
entries_size: integer;
|
||||||
|
entry_count: integer;
|
||||||
|
stream: TMemoryStream;
|
||||||
|
i: integer;
|
||||||
|
begin
|
||||||
|
section.data := mem_data;
|
||||||
|
entries_offset := (pinteger(mem_data))^; //offset relative to section beginning
|
||||||
|
entries_size := (pinteger(mem_data + 4))^; //length in bytes
|
||||||
|
entry_count := entries_size div 32; //actual count of entries
|
||||||
|
writeln('entries: ', entry_count);
|
||||||
|
|
||||||
|
//load entries to stream for easier parsing
|
||||||
|
stream := TMemoryStream.Create;
|
||||||
|
stream.WriteBuffer((mem_data + entries_offset)^, entries_size);
|
||||||
|
stream.Seek(0, soBeginning);
|
||||||
|
|
||||||
|
SetLength(section.nodes, entry_count);
|
||||||
|
for i := 0 to entry_count - 1 do begin
|
||||||
|
section.nodes[i] := ReadEntry(stream, mem_data);
|
||||||
|
end;
|
||||||
|
|
||||||
|
stream.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure AddNode(const parent: PRsDatFileNode; var nodes: array of TRsDatFileNode; var node_index: integer);
|
||||||
|
var
|
||||||
|
i: integer;
|
||||||
|
node: PRsDatFileNode;
|
||||||
|
begin
|
||||||
|
if node_index > length(nodes) - 1 then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
//add current to parent
|
||||||
|
node := @nodes[node_index];
|
||||||
|
i := length(parent^.nodes);
|
||||||
|
Setlength(parent^.nodes, i + 1);
|
||||||
|
parent^.nodes[i] := node;
|
||||||
|
//writeln('added node: ', node^.name, ' to parent ', parent^.name);
|
||||||
|
|
||||||
|
//add all subentries if any
|
||||||
|
if node^.is_directory then begin
|
||||||
|
//writeln(' subentries: ', node^.subentries_count);
|
||||||
|
while CountSubNodes(node) < node^.subentries_count + 1 do begin
|
||||||
|
node_index += 1;
|
||||||
|
AddNode(node, nodes, node_index);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRSDatFile.ParseSectionStructure(var section: TRsHdrSection);
|
||||||
|
var
|
||||||
|
node_idx: integer = 0;
|
||||||
|
begin
|
||||||
|
section.root.name := section.name;
|
||||||
|
section.root.is_directory := true;
|
||||||
|
section.root.data := nil;
|
||||||
|
while node_idx < length(section.nodes) do begin
|
||||||
|
AddNode(@section.root, section.nodes, node_idx);
|
||||||
|
node_idx += 1;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure WriteDirectory(const node: PRsDatFileNode; const path: string; const data: pbyte);
|
||||||
|
var
|
||||||
|
dir: string;
|
||||||
|
subnode: PRsDatFileNode;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
dir := path + node^.name;
|
||||||
|
if not DirectoryExists(dir) then
|
||||||
|
MkDir(dir);
|
||||||
|
for i := 0 to length(node^.nodes) - 1 do begin
|
||||||
|
subnode := node^.nodes[i];
|
||||||
|
if subnode^.is_directory then
|
||||||
|
WriteDirectory(subnode, dir + DirectorySeparator, data)
|
||||||
|
else
|
||||||
|
SaveFile(dir + DirectorySeparator + subnode^.name, data + subnode^.offset, subnode^.size);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRSDatFile.WriteFilesToDirectory(const path: string);
|
||||||
|
var
|
||||||
|
section: TRsHdrSection;
|
||||||
|
begin
|
||||||
|
for section in m_sections do begin
|
||||||
|
WriteDirectory(@section.root, path, section.data);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end.
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user