// // The graphics engine GXScene // unit Formatx.B3D; (* File streaming class for the B3D loader *) interface {$I Stage.Defines.inc} {$R-} uses System.Classes, System.SysUtils, Stage.VectorGeometry, Stage.VectorTypes, GXS.VectorLists; type TB3DChunkType = (bctUnknown, bctHeader, bctTexture, bctBrush, bctNode, bctVertex, bctTriangle, bctMesh, bctBone, bctKeyFrame, bctAnimation); PB3DChunk = ^TB3DChunk; TB3DChunk = record chunk: array[0..3] of char; length: Integer; end; PBB3DChunk = ^TBB3DChunk; TBB3DChunk = record Version: Integer; end; PTEXSChunk = ^TTEXSChunk; TTEXSChunk = record fileName: array[0..255] of char; //texture file name this is the filename of the texture, ie "wall.bmp" Has to be in the local Directory flags, blend: Integer; //blitz3D TextureFLags and TextureBlend: default=1,2 //these are the same as far as I know as the flags for a texture in Blitz3D x_pos, y_pos: Single; //x and y position of texture: default=0,0 x_scale, y_scale: Single; //x and y scale of texture: default=1,1 rotation: Single; //rotation of texture (in radians): default=0 radian = 180/pi degrees end; PBRUSChunk = ^TBRUSChunk; TBRUSChunk = record n_texs: Integer; name: array[0..255] of Char; //eg "WATER" - just use texture name by default red, green, blue, alpha: Single; //Blitz3D Brushcolor and Brushalpha: default=1,1,1,1 shininess: Single; //Blitz3D BrushShininess: default=0 blend, fx: Integer; //Blitz3D Brushblend and BrushFX: default=1,0 texture_id: array of Integer; //textures used in brush, ie if there is more then one texture used, ie Alphamaps, colour maps etc, //you put all ID's here as ints. end; PVertexData = ^TVertexData; TVertexData = record next: PVertexData; x, y, z: Single; //always present nx, ny, nz: Single; //vertex normal: present if (flags&1) red, green, blue, alpha: Single; //vertex color: present if (flags&2) tex_coords: array of Single; //tex coords end; PVRTSChunk = ^TVRTSChunk; TVRTSChunk = record flags: Integer; //1=normal values present, 2=rgba values present tex_coord_sets: Integer; //texture coords per vertex (eg: 1 for simple U/V) max=8 tex_coord_set_size: Integer; //components per set (eg: 2 for simple U/V) max=4 vertices: PVertexData; end; PTRISChunk = ^TTRISChunk; TTRISChunk = record next: PTRISChunk; brush_id: Integer; //brush applied to these TRIs: default=-1 vertex_id: array of Integer; //vertex indices end; PMESHChunk = ^TMESHChunk; TMESHChunk = record brush_id: Integer; //'master' brush: default=-1 vertices: TVRTSChunk; //vertices triangles: PTRISChunk; //1 or more sets of triangles end; PBONEChunk = ^TBONEChunk; TBONEChunk = record vertex_id: Integer; //vertex affected by this bone weight: Single; //;how much the vertex is affected end; PKEYSChunk = ^TKEYSChunk; TKEYSChunk = record next: PKEYSChunk; flags: Integer; //1=position, 2=scale, 4=rotation frame: Integer; //where key occurs position: TAffineVector; //present if (flags&1) scale: TAffineVector; //present if (flags&2) rotation: TGLVector; //present if (flags&4) end; PANIMChunk = ^TANIMChunk; TANIMChunk = record flags: Integer; //unused: default=0 frames: Integer; //how many frames in anim fps: Single; //default=60 end; PNODEChunk = ^TNODEChunk; TNODEChunk = record name: array[0..255] of char; //name of node position: TAffineVector; //local... scale: TAffineVector; //coord... rotation: TGLVector; //system... //array of node elements //should be one of meshes or bones, support meshes only for now meshes: PMESHChunk; //what 'kind' of node this is - if unrecognized, just use a Blitz3D pivot. (* not supprot yet bones: PBONEChunk; *) keys: PKEYSChunk; //optional animation keys nodes: PNODEChunk; //optional child nodes animation: TANIMChunk; //optional animation next: PNODEChunk; //point to the next node level: Integer; end; type TB3DMaterial = class public MaterialData: TBRUSChunk; constructor Create; destructor Destroy; override; function GetMaterialName: string; end; TB3DTexture = class public TextureData: TTEXSChunk; constructor Create; destructor Destroy; override; function GetTextureName: string; end; TB3DNode = class public NodeData: PNODEChunk; constructor Create; destructor Destroy; override; function GetNodeName: string; procedure DestroyNodeData(Node: PNODEChunk); end; TFileB3D = class private fTextures: TStringList; fMaterials: TStringList; fNodes: TB3DNode; procedure FreeLists; function GetChunkType(const aChunk: TB3DChunk): TB3DChunkType; function SkipChunk(aStream: TStream; const aChunk: TB3DChunk): Integer; function ReadTextureChunk(aStream: TStream; const aChunk: TB3DChunk): Integer; function ReadMaterialChunk(aStream: TStream; const aChunk: TB3DChunk): Integer; function ReadNodeChunk(aStream: TStream; const aChunk: TB3DChunk; Node: PNODEChunk; level: Integer): Integer; function ReadMeshChunk(aStream: TStream; const aChunk: TB3DChunk; Mesh: PMESHChunk): Integer; function ReadVerticesChunk(aStream: TStream; const aChunk: TB3DChunk; Vertices: PVRTSChunk): Integer; function ReadTrianglesChunk(aStream: TStream; const aChunk: TB3DChunk; Triangle: PTRISChunk): Integer; public constructor Create; virtual; destructor Destroy; override; procedure LoadFromStream(aStream : TStream); //for test only procedure Check; property Textures: TStringList read fTextures; property Materials: TStringList read fMaterials; property Nodes: TB3DNode read fNodes; end; //----------------------------------------------------------------------- implementation //----------------------------------------------------------------------- constructor TB3DMaterial.Create; begin inherited Create; fillChar(MaterialData, sizeof(TBRUSChunk), 0); end; destructor TB3DMaterial.Destroy; begin SetLength(MaterialData.texture_id, 0); inherited Destroy; end; function TB3DMaterial.GetMaterialName: string; begin SetString(Result, MaterialData.name, strlen(MaterialData.name)); end; constructor TB3DTexture.Create; begin inherited Create; fillChar(TextureData, sizeof(TTEXSChunk), 0); end; destructor TB3DTexture.Destroy; begin inherited Destroy; end; function TB3DTexture.GetTextureName: string; begin SetString(Result, TextureData.fileName, strlen(TextureData.fileName)); end; constructor TB3DNode.Create; begin inherited Create; NodeData := nil; end; destructor TB3DNode.Destroy; begin DestroyNodeData(NodeData); inherited Destroy; end; function TB3DNode.GetNodeName: string; begin SetString(Result, NodeData^.name, strlen(NodeData^.name)); end; procedure DeleteVertices(var aVertex: PVertexData); var V: PVertexData; begin while aVertex<>nil do begin SetLength(aVertex^.tex_coords, 0); V := aVertex^.next; freeMem(aVertex); aVertex := nil; DeleteVertices(V); end; end; procedure DeleteTriangles(var aTriangle: PTRISChunk); var T: PTRISChunk; begin while aTriangle<>nil do begin SetLength(aTriangle^.vertex_id, 0); T := aTriangle^.next; freeMem(aTriangle); aTriangle := nil; DeleteTriangles(T); end; end; procedure TB3DNode.DestroyNodeData(Node: PNODEChunk); var oldNode, PNode: PNODEChunk; begin PNode := Node; while PNode<>nil do begin if PNode^.meshes<>nil then begin DeleteTriangles(PNode^.meshes^.triangles); DeleteVertices(PNode^.meshes^.vertices.vertices); freeMem(PNode^.meshes); PNode^.meshes := nil; end; if PNode^.keys<>nil then freeMem(PNode^.keys); DestroyNodeData(PNode^.nodes); oldNode := PNode; PNode := PNode^.next; freeMem(oldNode); end; end; //------------------------------------------------------------------------------ constructor TFileB3D.Create; begin inherited Create; fTextures := TStringList.Create; fMaterials := TStringList.Create; fNodes := TB3DNode.Create; end; destructor TFileB3D.Destroy; begin FreeLists; fTextures.free; fMaterials.free; fNodes.free; inherited Destroy; end; function TFileB3D.GetChunkType(const aChunk: TB3DChunk): TB3DChunkType; begin Result := bctUnKnown; if StrLIComp(aChunk.chunk, 'BB3D', 4)=0 then Result := bctHeader; if StrLIComp(aChunk.chunk, 'TEXS', 4)=0 then Result := bctTexture; if StrLIComp(aChunk.chunk, 'BRUS', 4)=0 then Result := bctBrush; if StrLIComp(aChunk.chunk, 'NODE', 4)=0 then Result := bctNode; if StrLIComp(aChunk.chunk, 'VRTS', 4)=0 then Result := bctVertex; if StrLIComp(aChunk.chunk, 'BONE', 4)=0 then Result := bctBone; if StrLIComp(aChunk.chunk, 'KEYS', 4)=0 then Result := bctKeyFrame; if StrLIComp(aChunk.chunk, 'ANIM', 4)=0 then Result := bctAnimation; if StrLIComp(aChunk.chunk, 'MESH', 4)=0 then Result := bctMesh; if StrLIComp(aChunk.chunk, 'TRIS', 4)=0 then Result := bctTriangle; end; function TFileB3D.SkipChunk(aStream: TStream; const aChunk: TB3DChunk): Integer; begin aStream.Seek(aChunk.length, soFromCurrent); Result := aChunk.length; end; function ReadString(aStream: TStream; buffer: PChar; MaxCount: Integer): Integer; begin Result := 0; while Result0 then begin Inc(Result, aStream.Read(v1^.nx, sizeof(single))); Inc(Result, aStream.Read(v1^.ny, sizeof(single))); Inc(Result, aStream.Read(v1^.nz, sizeof(single))); end; if (Vertices^.flags and 2)>0 then begin Inc(Result, aStream.Read(v1^.red, sizeof(single))); Inc(Result, aStream.Read(v1^.green, sizeof(single))); Inc(Result, aStream.Read(v1^.blue, sizeof(single))); Inc(Result, aStream.Read(v1^.alpha, sizeof(single))); end; //W3D END SetLength(v1^.tex_coords, size); Inc(Result, aStream.Read(v1^.tex_coords[0], size*sizeof(single))); end; end; function TFileB3D.ReadTrianglesChunk(aStream: TStream; const aChunk: TB3DChunk; Triangle: PTRISChunk): Integer; begin Result := 0; if Triangle=nil then begin GetMem(Triangle, sizeof(TTRISChunk)); fillChar(Triangle^, sizeof(TTRISChunk), 0); Triangle^.brush_id := -1; end; Inc(Result, aStream.Read(Triangle^.brush_id, sizeof(Integer))); SetLength(Triangle^.vertex_id, (aChunk.length-Result) div sizeof(Integer)); Inc(Result, aStream.Read(Triangle^.vertex_id[0], (aChunk.length-Result))); end; //read in only the mesh data, the keyframes and animation had been dropped function TFileB3D.ReadNodeChunk(aStream: TStream; const aChunk: TB3DChunk; Node: PNODEChunk; level: Integer): Integer; var Count: Integer; C: TB3DChunk; N: PNODEChunk; begin N := nil; fillChar(Node^, sizeof(TNODEChunk), 0); Node^.level := level; Count := 0; Inc(Count, ReadString(aStream, Node^.name, 255)); Inc(Count, aStream.Read(Node^.position.X, sizeof(TAffineVector))); Inc(Count, aStream.Read(Node^.scale.X, sizeof(TAffineVector))); Inc(Count, aStream.Read(Node^.rotation.X, sizeof(TGLVector))); while Count0 do begin fTextures.Objects[0].free; ftextures.Delete(0); end; while fMaterials.Count>0 do begin fMaterials.Objects[0].free; fMaterials.Delete(0); end; end; //for test only procedure TFileB3D.Check; var NodeLevel: Integer; // NodeCount: Integer; Node: PNODEChunk; // VerticesCount: Integer; // FaceCount: Integer; Face: PTRISChunk; Vertex: PVertexData; begin NodeLevel := 0; // NodeCount := 0; // VerticesCount := 0; // FaceCount := 0; Node := fNodes.NodeData; while Node<>nil do begin if Node^.meshes<>nil then // Inc(NodeCount); if Node^.level>NodeLevel then NodeLevel := Node^.level; if Node^.meshes<>nil then begin Vertex := Node^.meshes.vertices.vertices; while Vertex<>nil do begin // Inc(VerticesCount); Vertex := Vertex.next; end; Face := Node^.meshes.triangles; while Face<>nil do begin // Inc(FaceCount); Face := Face.next; end; end; Node := Node^.next; end; //MessageBeep(FaceCount); //MessageBeep(VerticesCount); //MessageBeep(NodeLevel); //MessageBeep(NodeCount); end; end.