【Spine】Spine Runtime for Delphi移植笔记(六…
2018-06-17 18:56:30来源:未知 阅读 ()
//////////////////////////////////////////////////////////////////////////////// //Generic delphi runtime v3.6 for Spine animation tool // //Runtime port by cjk (hzi1980@163.com) // //////////////////////////////////////////////////////////////////////////////// unit spine.core.skeleton.binary; interface uses System.Classes, System.SysUtils, System.Generics.Collections, System.Math, spine.types, spine.classes, spine.data, spine.core.atlas, spine.core.bone, spine.core.slot, spine.core.skin, spine.core.attachment, spine.core.constraint, spine.core.skeleton, spine.core.animation.timeline, spine.core.skeleton.json, spine.core.event, spine.core.animation; type SByte = ShortInt; TSpineSkeletonBinary = class public const BONE_ROTATE = 0; BONE_TRANSLATE = 1; BONE_SCALE = 2; BONE_SHEAR = 3; SLOT_ATTACHMENT = 0; SLOT_COLOR = 1; SLOT_TWO_COLOR = 2; PATH_POSITION = 0; PATH_SPACING = 1; PATH_MIX = 2; CURVE_LINEAR = 0; CURVE_STEPPED = 1; CURVE_BEZIER = 2; private type TVertices = record Bones: TArray<Integer>; Vertices: TArray<Single>; end; private FLinkedMeshes: TObjectList<TSkeletonJson.TLinkedMesh>; FAttachmentLoader: TAttachmentLoader; FBuffer: array [0..31] of Byte; function ReadByte(const AStream: TStream): Integer; function ReadString(const AStream: TStream): string; function ReadInt(const AStream: TStream): Integer; function ReadFloat(const AStream: TStream): Single; function ReadBoolean(const AStream: TStream): Boolean; function ReadSByte(const AStream: TStream): SByte; function ReadVarInt(const AStream: TStream; const AOptimizePositive: Boolean): Integer; function ReadShortArray(const AStream: TStream): TArray<Integer>; function ReadFloatArray(const AStream: TStream; const ACount: Integer; const AScale: Single): TArray<Single>; function ReadVertices(const AStream: TStream; const AVertexCount: Integer): TVertices; function ReadAttachment(const AStream: TStream; const ASkeletonData: TSkeletonData; const ASkin: TSpineSkin; const ASlotIndex: Integer; const AAttachmentName: string; const ANonessential: Boolean): IAttachment; function ReadSkin (const AStream: TStream; const ASkeletonData: TSkeletonData; const ASkinName: string; const ANonessential: Boolean): TSpineSkin; procedure ReadCurve(const AStream: TStream; const AFrameIndex: Integer; const ATimeline: TCurveTimeline); procedure ReadAnimation(const AName: string; const AStream: TStream; const ASkeletonData: TSkeletonData); class procedure ReadFully(const AStream: TStream; var ABuffer: TArray<Byte>; const AOffset: Integer; var ALength: Integer); static; public Scale: Single; constructor Create(const AAtlasArray: TArray<TSpineAtlas>); overload; constructor Create(const AAttachmentLoader: TAttachmentLoader); overload; destructor Destroy; override; function ReadSkeletonData(const ASkelFile: string): TSkeletonData; overload; function ReadSkeletonData(const ASkelStream: TStream): TSkeletonData; overload; end; implementation { TSpineSkeletonBinary } constructor TSpineSkeletonBinary.Create(const AAtlasArray: TArray<TSpineAtlas>); begin FAttachmentLoader:= TAtlasAttachmentLoader.Create(AAtlasArray); Create(FAttachmentLoader); end; constructor TSpineSkeletonBinary.Create( const AAttachmentLoader: TAttachmentLoader); begin inherited Create; if not Assigned(AAttachmentLoader) then raise Exception.Create('attachmentLoader cannot be null.'); FLinkedMeshes:= TObjectList<TSkeletonJson.TLinkedMesh>.Create; FAttachmentLoader:= AAttachmentLoader; end; destructor TSpineSkeletonBinary.Destroy; begin FLinkedMeshes.Free; if Assigned(FAttachmentLoader) then FreeAndNil(FAttachmentLoader); inherited; end; function TSpineSkeletonBinary.ReadSkeletonData( const ASkelFile: string): TSkeletonData; var lStream: TFileStream; begin lStream:= TFileStream.Create(ASkelFile, fmOpenRead); try result:= Self.ReadSkeletonData(lStream); finally lStream.Free; end; end; function TSpineSkeletonBinary.ReadSkeletonData( const ASkelStream: TStream): TSkeletonData; var lScale: Single; lNonessential: Boolean; i, j, n, nn: Integer; lName: string; lBoneDataParent, lBoneData: TBoneData; lColor, lDarkColor: Integer; lSlotData: TSlotData; lIkConstraintData: TIkConstraintData; lTransformConstraintData: TTransformConstraintData; lPathConstraintData: TPathConstraintData; lDefaultSkin, lSkin: TSpineSkin; lLinkedMesh: TSkeletonJson.TLinkedMesh; lParentAttachment: IAttachment; lEventData: TEventData; begin if not Assigned(ASkelStream) then raise Exception.Create('skelstream cannot be null.'); result:= TSkeletonData.Create; result.Name:= ChangeFileExt(ExtractFileName(TFileStream(ASkelStream).FileName),''); result.Hash:= Self.ReadString(ASkelStream); result.Version:= Self.ReadString(ASkelStream); result.Width:= Self.ReadFloat(ASkelStream); result.Height:= Self.ReadFloat(ASkelStream); lNonessential:= Self.ReadBoolean(ASkelStream); if lNonessential then begin result.FPS:= Self.ReadFloat(ASkelStream); result.ImagesPath:= Self.ReadString(ASkelStream); end; // Bones. n:= Self.ReadVarInt(ASkelStream, True); for i:= 0 to n -1 do begin lName:= Self.ReadString(ASkelStream); if i = 0 then lBoneDataParent:= nil else lBoneDataParent:= result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)]; lBoneData:= TBoneData.Create(i, lName, lBoneDataParent); lBoneData.Rotation:= Self.ReadFloat(ASkelStream); lBoneData.X:= Self.ReadFloat(ASkelStream) * Self.Scale; lBoneData.Y:= Self.ReadFloat(ASkelStream) * Self.Scale; lBoneData.ScaleX:= Self.ReadFloat(ASkelStream); lBoneData.ScaleY:= Self.ReadFloat(ASkelStream); lBoneData.ShearX:= Self.ReadFloat(ASkelStream); lBoneData.ShearY:= Self.ReadFloat(ASkelStream); lBoneData.Length:= Self.ReadFloat(ASkelStream) * Self.Scale; //lBoneData.TransformMode:= TTransformModes[Self.ReadVarInt(ASkelStream, True)]; if lNonessential then Self.ReadInt(ASkelStream); // Skip bone color. result.BoneDatas.Add(lBoneData); end; // Slots. n:= Self.ReadVarInt(ASkelStream, True); for i:= 0 to n -1 do begin lName:= Self.ReadString(ASkelStream); lBoneData:= result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)]; lSlotData:= TSlotData.Create(i, lName, lBoneData); lColor:= Self.ReadInt(ASkelStream); lSlotData.R:= ((lColor and $ff) shr 24) / 255; lSlotData.G:= ((lColor and $00ff) shr 16) / 255; lSlotData.B:= ((lColor and $0000ff) shr 8) / 255; lSlotData.A:= (lColor and $000000ff) / 255; lDarkColor:= Self.ReadInt(ASkelStream); // 0x00rrggbb if lDarkColor <> -1 then begin lSlotData.HasSecondColor:= True; lSlotData.R2:= ((lDarkColor and $00ff) shr 16) / 255; lSlotData.G2:= ((lDarkColor and $0000ff) shr 8) / 255; lSlotData.B2:= (lDarkColor and $000000ff) / 255; end; lSlotData.AttachmentName:= Self.ReadString(ASkelStream); lSlotData.BlendMode:= TBlendMode(Self.ReadVarInt(ASkelStream, True)); result.SlotDatas.Add(lSlotData); end; // IK constraints. n:= Self.ReadVarInt(ASkelStream, True); for i:= 0 to n -1 do begin lIkConstraintData:= TIkConstraintData.Create(Self.ReadString(ASkelStream)); lIkConstraintData.Order:= Self.ReadVarInt(ASkelStream, True); nn:= Self.ReadVarInt(ASkelStream, True); for j:= 0 to nn -1 do lIkConstraintData.BoneDatas.Add(result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)]); lIkConstraintData.Target:= result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)]; lIkConstraintData.Mix:= Self.ReadFloat(ASkelStream); lIkConstraintData.BendDirection:= Self.ReadSByte(ASkelStream); result.IkConstraintDatas.Add(lIkConstraintData); end; // Transform constraints. n:= Self.ReadVarInt(ASkelStream, True); for i:= 0 to n -1 do begin lTransformConstraintData:= TTransformConstraintData.Create(Self.ReadString(ASkelStream)); lTransformConstraintData.Order:= Self.ReadVarInt(ASkelStream, True); nn:= Self.ReadVarInt(ASkelStream, True); for j:= 0 to nn -1 do lTransformConstraintData.BoneDatas.Add(result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)]); lTransformConstraintData.Target:= result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)]; lTransformConstraintData.Local:= Self.ReadBoolean(ASkelStream); lTransformConstraintData.Relative:= Self.ReadBoolean(ASkelStream); lTransformConstraintData.OffsetRotation:= Self.ReadFloat(ASkelStream); lTransformConstraintData.OffsetX:= Self.ReadFloat(ASkelStream) * Self.Scale; lTransformConstraintData.OffsetY:= Self.ReadFloat(ASkelStream) * Self.Scale; lTransformConstraintData.OffsetScaleX:= Self.ReadFloat(ASkelStream); lTransformConstraintData.OffsetScaleY:= Self.ReadFloat(ASkelStream); lTransformConstraintData.OffsetShearY:= Self.ReadFloat(ASkelStream); lTransformConstraintData.RotateMix:= Self.ReadFloat(ASkelStream); lTransformConstraintData.TranslateMix:= Self.ReadFloat(ASkelStream); lTransformConstraintData.ScaleMix:= Self.ReadFloat(ASkelStream); lTransformConstraintData.ShearMix:= Self.ReadFloat(ASkelStream); result.TransformConstraintDatas.Add(lTransformConstraintData); end; // Path constraints n:= Self.ReadVarInt(ASkelStream, True); for i:= 0 to n -1 do begin lPathConstraintData:= TPathConstraintData.Create(Self.ReadString(ASkelStream)); lPathConstraintData.Order:= Self.ReadVarInt(ASkelStream, True); nn:= Self.ReadVarInt(ASkelStream, True); for j:= 0 to nn -1 do lPathConstraintData.BoneDatas.Add(result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)]); lPathConstraintData.Target:= result.SlotDatas.Items[Self.ReadVarint(ASkelStream, true)]; lPathConstraintData.PositionMode:= TPositionMode(Self.ReadVarint(ASkelStream, true)); lPathConstraintData.SpacingMode:= TSpacingMode(Self.ReadVarint(ASkelStream, true)); lPathConstraintData.RotateMode:= TRotateMode(Self.ReadVarint(ASkelStream, true)); lPathConstraintData.OffsetRotation:= Self.ReadFloat(ASkelStream); lPathConstraintData.Position:= Self.ReadFloat(ASkelStream); if lPathConstraintData.PositionMode = TPositionMode.pmFixed then lPathConstraintData.Position:= lPathConstraintData.Position * Self.Scale; lPathConstraintData.Spacing:= Self.ReadFloat(ASkelStream); if (lPathConstraintData.SpacingMode = TSpacingMode.smLength) or (lPathConstraintData.SpacingMode = TSpacingMode.smFixed) then lPathConstraintData.Spacing:= lPathConstraintData.Spacing * Self.Scale; lPathConstraintData.RotateMix:= Self.ReadFloat(ASkelStream); lPathConstraintData.TranslateMix:= Self.ReadFloat(ASkelStream); result.PathConstraintDatas.Add(lPathConstraintData); end; // Default skin. lDefaultSkin:= Self.ReadSkin(ASkelStream, result, 'default', lNonessential); if Assigned(lDefaultSkin) then begin result.DefaultSkin:= lDefaultSkin; result.Skins.Add(lDefaultSkin); end; // Skins. n:= Self.ReadVarInt(ASkelStream, True); for i:= 0 to n -1 do result.Skins.Add(Self.ReadSkin(ASkelStream, result, Self.ReadString(ASkelStream), lNonessential)); // Linked meshes. n:= FLinkedMeshes.Count; for i:= 0 to n -1 do begin lLinkedMesh:= FLinkedMeshes[i]; if not Assigned(lLinkedMesh) then lSkin:= lDefaultSkin else lSkin:= result.FindSkin(lLinkedMesh.Skin); if not Assigned(lSkin) then raise Exception.CreateFmt('Skin not found: %s',[lLinkedMesh.Skin]); lParentAttachment:= lSkin.GetAttachment(lLinkedMesh.SlotIndex, lLinkedMesh.Parent); if not Assigned(lParentAttachment) then raise Exception.CreateFmt('Parent mesh not found: %s',[lLinkedMesh.Parent]); lLinkedMesh.Mesh.ParentMesh:= TMeshAttachment(lParentAttachment); lLinkedMesh.Mesh.UpdateUVs; end; FLinkedMeshes.Clear; // Events. n:= Self.ReadVarInt(ASkelStream, True); for i:= 0 to n -1 do begin lEventData:= TEventData.Create(Self.ReadString(ASkelStream)); lEventData.IntValue:= Self.ReadVarInt(ASkelStream, False); lEventData.FloatValue:= Self.ReadFloat(ASkelStream); lEventData.StringValue:= Self.ReadString(ASkelStream); result.EventDatas.Add(lEventData); end; // Animations. n:= Self.ReadVarInt(ASkelStream, True); for i:= 0 to n -1 do Self.ReadAnimation(Self.ReadString(ASkelStream), ASkelStream, result); result.BoneDatas.TrimExcess; result.SlotDatas.TrimExcess; result.Skins.TrimExcess; result.EventDatas.TrimExcess; result.Animations.TrimExcess; result.IkConstraintDatas.TrimExcess; result.TransformConstraintDatas.TrimExcess; result.PathConstraintDatas.TrimExcess; end; function TSpineSkeletonBinary.ReadByte(const AStream: TStream): Integer; begin if AStream.Position + 1 > AStream.Size then exit(-1); AStream.Read(result, 1); end; function TSpineSkeletonBinary.ReadSkin(const AStream: TStream; const ASkeletonData: TSkeletonData; const ASkinName: string; const ANonessential: Boolean): TSpineSkin; var lSlotCount, i, lSlotIndex, j, n: Integer; lName: string; lAttachment: IAttachment; begin lSlotCount:= Self.ReadVarInt(AStream, True); if lSlotCount = 0 then exit(nil); result:= TSpineSkin.Create; for i:= 0 to lSlotCount -1 do begin lSlotIndex:= Self.ReadVarInt(AStream, True); n:= Self.ReadVarInt(AStream, True); for j:= 0 to n -1 do begin lName:= Self.ReadString(AStream); lAttachment:= Self.ReadAttachment(AStream, ASkeletonData, result, lSlotIndex, lName, ANonessential); if Assigned(lAttachment) then result.AddAttachment(lSlotIndex, lName, lAttachment); end; end; end; function TSpineSkeletonBinary.ReadAttachment(const AStream: TStream; const ASkeletonData: TSkeletonData; const ASkin: TSpineSkin; const ASlotIndex: Integer; const AAttachmentName: string; const ANonessential: Boolean): IAttachment; var lName, lPath: string; lAttachmentType: TAttachmentType; lRotation, lX, lY, lScaleX, lScaleY, lWidth, lHeight: Single; lColor: Integer; lRegionAttachment: TRegionAttachment; lVertexCount: Integer; lVertices: TVertices; lBoxAttachment: TBoundingBoxAttachment; lUVs: TArray<Single>; lTriangles, lEdges: TArray<Integer>; lHullLength: Integer; lMeshAttachment: TMeshAttachment; lSkinName, lParentMeshName: string; lInheritDeform: Boolean; lClosed, lConstantSpeed: Boolean; lLengths: TArray<Single>; i: Integer; lPathAttachment: TPathAttachment; lPointAttachment: TPointAttachment; lEndSlotIndex: Integer; lClippingAttachment: TClippingAttachment; begin lName:= Self.ReadString(AStream); if lName.Trim.IsEmpty then lName:= AAttachmentName; lAttachmentType:= TAttachmentType(Self.ReadByte(AStream)); case lAttachmentType of TAttachmentType.atRegion: begin lPath:= Self.ReadString(AStream); lRotation:= Self.ReadFloat(AStream); lX:= Self.ReadFloat(AStream); lY:= Self.ReadFloat(AStream); lScaleX:= Self.ReadFloat(AStream); lScaleY:= Self.ReadFloat(AStream); lWidth:= Self.ReadFloat(AStream); lHeight:= Self.ReadFloat(AStream); lColor:= Self.ReadInt(AStream); if lPath.Trim.IsEmpty then lPath:= lName; // lRegionAttachment:= FAttachmentLoader.NewRegionAttachment(ASkin, lName, lPath); if not Assigned(lRegionAttachment) then exit(nil); lRegionAttachment.Path:= lPath; lRegionAttachment.X:= lX * Self.Scale; lRegionAttachment.Y:= lY * Self.Scale; lRegionAttachment.ScaleX:= lScaleX; lRegionAttachment.ScaleY:= lScaleY; lRegionAttachment.Rotation:= lRotation; lRegionAttachment.Width:= lWidth * Self.Scale; lRegionAttachment.Height:= lHeight * Self.Scale; lRegionAttachment.R:= ((lColor and $ff) shr 24) / 255; lRegionAttachment.G:= ((lColor and $00ff) shr 16) / 255; lRegionAttachment.B:= ((lColor and $0000ff) shr 8) / 255; lRegionAttachment.A:= ((lColor and $000000ff)) / 255; lRegionAttachment.UpdateOffset(); exit(lRegionAttachment); end; TAttachmentType.atBoundingbox: begin lVertexCount:= Self.ReadVarint(AStream, True); lVertices:= Self.ReadVertices(AStream, lVertexCount); if ANonessential then Self.ReadInt(AStream); // lBoxAttachment:= FAttachmentLoader.NewBoundingBoxAttachment(ASkin, lName); if not Assigned(lBoxAttachment) then exit(nil); lBoxAttachment.WorldVerticesLength:= lVertexCount shl 1; SetLength(lBoxAttachment.Vertices, Length(lVertices.Vertices)); SetLength(lBoxAttachment.Bones, Length(lVertices.Bones)); TArray.Copy<Single>(lVertices.Vertices, lBoxAttachment.Vertices, 0, 0, Length(lVertices.Vertices)); TArray.Copy<Integer>(lVertices.Bones, lBoxAttachment.Bones, 0, 0, Length(lVertices.Bones)); exit(lBoxAttachment); end; TAttachmentType.atMesh: begin lPath:= Self.ReadString(AStream); lColor:= Self.ReadInt(AStream); lVertexCount:= Self.ReadVarInt(AStream, True); lUVs:= Self.ReadFloatArray(AStream, lVertexCount shl 1, 1); lTriangles:= Self.ReadShortArray(AStream); lVertices:= Self.ReadVertices(AStream, lVertexCount); lHullLength:= Self.ReadVarInt(AStream, True); lWidth:= 0; lHeight:= 0; if ANonessential then begin lEdges:= Self.ReadShortArray(AStream); lWidth:= Self.ReadFloat(AStream); lHeight:= Self.ReadFloat(AStream); end; if lPath.Trim.IsEmpty then lPath:= lName; lMeshAttachment:= FAttachmentLoader.NewMeshAttachment(ASkin, lName, lPath); if not Assigned(lMeshAttachment) then exit(nil); lMeshAttachment.Path:= lPath; lMeshAttachment.R:= ((lColor and $ff) shr 24) / 255; lMeshAttachment.G:= ((lColor and $00ff) shr 16) / 255; lMeshAttachment.B:= ((lColor and $0000ff) shr 8) / 255; lMeshAttachment.A:= ((lColor and $000000ff)) / 255; SetLength(lMeshAttachment.Vertices, Length(lVertices.Vertices)); SetLength(lMeshAttachment.Bones, Length(lVertices.Bones)); TArray.Copy<Single>(lVertices.Vertices, lMeshAttachment.Vertices, 0, 0, Length(lVertices.Vertices)); TArray.Copy<Integer>(lVertices.Bones, lMeshAttachment.Bones, 0, 0, Length(lVertices.Bones)); lMeshAttachment.WorldVerticesLength:= lVertexCount shl 1; SetLength(lMeshAttachment.Triangles, Length(lTriangles)); SetLength(lMeshAttachment.RegionUVs, Length(lUVs)); TArray.Copy<Integer>(lTriangles, lMeshAttachment.Triangles, 0, 0, Length(lTriangles)); TArray.Copy<Single>(lUVs, lMeshAttachment.RegionUVs, 0, 0, Length(lUVs)); lMeshAttachment.UpdateUVs(); lMeshAttachment.HullLength:= lHullLength shl 1; if ANonessential then begin TArray.Copy<Integer>(lEdges, lMeshAttachment.Edges, 0, 0, Length(lEdges)); lMeshAttachment.Width:= lWidth * Self.Scale; lMeshAttachment.Height:= lHeight * Self.Scale; end; exit(lMeshAttachment); end; TAttachmentType.atLinkedmesh: begin lPath:= Self.ReadString(AStream); lColor:= Self.ReadInt(AStream); lSkinName:= Self.ReadString(AStream); lParentMeshName:= Self.ReadString(AStream); lInheritDeform:= Self.ReadBoolean(AStream); lWidth:= 0; lHeight:= 0; if ANonessential then begin lWidth:= Self.ReadFloat(AStream); lHeight:= Self.ReadFloat(AStream); end; if lPath.Trim.IsEmpty then lPath:= lName; lMeshAttachment:= FAttachmentLoader.NewMeshAttachment(ASkin, lName, lPath); if not Assigned(lMeshAttachment) then exit(nil); lMeshAttachment.Path:= lPath; lMeshAttachment.R:= ((lColor and $ff) shr 24) / 255; lMeshAttachment.G:= ((lColor and $00ff) shr 16) / 255; lMeshAttachment.B:= ((lColor and $0000ff) shr 8) / 255; lMeshAttachment.A:= ((lColor and $000000ff)) / 255; lMeshAttachment.InheritDeform:= lInheritDeform; if ANonessential then begin lMeshAttachment.Width:= lWidth * Self.Scale; lMeshAttachment.Height:= lHeight * Self.Scale; end; FLinkedMeshes.Add(TSkeletonJson.TLinkedMesh.Create(lMeshAttachment, lSkinName, ASlotIndex, lParentMeshName)); exit(lMeshAttachment); end; TAttachmentType.atPath: begin lClosed:= Self.ReadBoolean(AStream); lConstantSpeed:= Self.ReadBoolean(AStream); lVertexCount:= Self.ReadVarint(AStream, True); lVertices:= Self.ReadVertices(AStream, lVertexCount); SetLength(lLengths, System.Math.Floor(lVertexCount / 3)); for i:= 0 to Length(lLengths) -1 do lLengths[i]:= Self.ReadFloat(AStream) * Self.Scale; if ANonessential then Self.ReadInt(AStream); // lPathAttachment:= FAttachmentLoader.NewPathAttachment(ASkin, lName); if not Assigned(lPathAttachment) then exit(nil); lPathAttachment.Closed:= lClosed; lPathAttachment.ConstantSpeed:= lConstantSpeed; lPathAttachment.WorldVerticesLength:= lVertexCount shl 1; SetLength(lPathAttachment.Vertices, Length(lVertices.Vertices)); SetLength(lPathAttachment.Bones, Length(lVertices.Bones)); SetLength(lPathAttachment.Lengths, Length(lLengths)); TArray.Copy<Single>(lVertices.Vertices, lPathAttachment.Vertices, 0, 0, Length(lVertices.Vertices)); TArray.Copy<Integer>(lVertices.Bones, lPathAttachment.Bones, 0, 0, Length(lVertices.Bones)); TArray.Copy<Single>(lLengths, lPathAttachment.Lengths, 0, 0, Length(lLengths)); exit(lPathAttachment); end; TAttachmentType.atPoint: begin lRotation:= Self.ReadFloat(AStream); lX:= Self.ReadFloat(AStream); lY:= Self.ReadFloat(AStream); if ANonessential then Self.ReadInt(AStream); // lPointAttachment:= FAttachmentLoader.NewPointAttachment(ASkin, lName); if not Assigned(lPointAttachment) then exit(nil); lPointAttachment.X:= lX * Self.Scale; lPointAttachment.Y:= lY * Self.Scale; lPointAttachment.Rotation:= lRotation; exit(lPointAttachment); end; TAttachmentType.atClipping: begin lEndSlotIndex:= Self.ReadVarint(AStream, True); lVertexCount:= Self.ReadVarint(AStream, True); lVertices:= Self.ReadVertices(AStream, lVertexCount); if ANonessential then Self.ReadInt(AStream); // lClippingAttachment:= FAttachmentLoader.NewClippingAttachment(ASkin, lName); if not Assigned(lClippingAttachment) then exit(nil); lClippingAttachment.EndSlot:= ASkeletonData.SlotDatas.Items[lEndSlotIndex]; lClippingAttachment.worldVerticesLength:= lVertexCount shl 1; SetLength(lPathAttachment.Vertices, Length(lVertices.Vertices)); SetLength(lPathAttachment.Bones, Length(lVertices.Bones)); TArray.Copy<Single>(lVertices.Vertices, lClippingAttachment.Vertices, 0, 0, Length(lVertices.Vertices)); TArray.Copy<Integer>(lVertices.Bones, lClippingAttachment.Bones, 0, 0, Length(lVertices.Bones)); exit(lClippingAttachment); end; end; result:= nil; end; function TSpineSkeletonBinary.ReadVertices(const AStream: TStream; const AVertexCount: Integer): TVertices; var lVerticesLength, i, lBoneCount, j, idx1, idx2: Integer; lWeights: TArray<Single>; begin lVerticesLength:= AVertexCount shl 1; if not Self.ReadBoolean(AStream) then begin result.Vertices:= Self.ReadFloatArray(AStream, lVerticesLength, Self.Scale); exit; end; SetLength(result.Vertices, lVerticesLength * 3 * 3); SetLength(result.Bones, lVerticesLength * 3); idx1:= 0; idx2:= 0; for i:= 0 to AVertexCount -1 do begin lBoneCount:= Self.ReadVarInt(AStream, True); result.Bones[idx1]:= Self.ReadVarInt(AStream, True); Inc(idx1); for j:= 0 to lBoneCount -1 do begin result.Bones[idx1]:= Self.ReadVarInt(AStream, True); Inc(idx1); // result.Vertices[idx2]:= Self.ReadFloat(AStream) * Self.Scale; Inc(idx2); result.Vertices[idx2]:= Self.ReadFloat(AStream) * Self.Scale; Inc(idx2); result.Vertices[idx2]:= Self.ReadFloat(AStream); Inc(idx2); end; end; end; function TSpineSkeletonBinary.ReadFloatArray(const AStream: TStream; const ACount: Integer; const AScale: Single): TArray<Single>; var i: Integer; begin SetLength(result, ACount); if AScale = 1 then begin for i:= 0 to ACount -1 do result[i]:= Self.ReadFloat(AStream); end else begin for i:= 0 to ACount -1 do result[i]:= Self.ReadFloat(AStream) * AScale; end; end; function TSpineSkeletonBinary.ReadShortArray( const AStream: TStream): TArray<Integer>; var lCount, i: Integer; begin lCount:= Self.ReadVarInt(AStream, True); SetLength(result, lCount); for i:= 0 to lCount -1 do result[i]:= (Self.ReadByte(AStream) shl 8) or Self.ReadByte(AStream); end; procedure TSpineSkeletonBinary.ReadAnimation(const AName: string; const AStream: TStream; const ASkeletonData: TSkeletonData); var lAnimation: TSpineAnimation; lDuration: Single; i, n, lIndex, j, nn, lTimelineType, lFrameCount, k, nnn: Integer; lAttachmentTimeline: TAttachmentTimeline; lFrameIndex: Integer; lColorTimeline: TColorTimeline; lTime, lR, lG, lB, lA, lR2, lG2, lB2: Single; lColor, lColor2: Integer; lTwoColorTimeline: TTwoColorTimeline; lRotateTimeline: TRotateTimeline; lTranslateTimeline: TTranslateTimeline; lTimelineScale: Single; lIkConstraintTimeline: TIkConstraintTimeline; lTransformConstraintTimeline: TTransformConstraintTimeline; lPathConstraintData: TPathConstraintData; lPathConstraintPositionTimeline: TPathConstraintPositionTimeline; lPathConstraintMixTimeline: TPathConstraintMixTimeline; lSkin: TSpineSkin; lVertexAttachment: TVertexAttachment; lWeighted: Boolean; v: Integer; lDeformLength: Integer; lDeformTiemline: TDeformTimeline; lDeform: TArray<Single>; lStart, lEnd: Integer; lDrawOrderTimeline: TDrawOrderTimeline; lSlotCount, lOffsetCount: Integer; lDrawOrder, lUnChanged: TArray<Integer>; lOriginalIndex, lUnChangedIndex: Integer; lEventTimeline: TEventTimeline; lEventData: TEventData; lEvent: TSpineEvent; begin lAnimation:= TSpineAnimation.Create(AName, 0); try lDuration:= 0; // Slot timelines. n:= Self.ReadVarInt(AStream, True); for i:= 0 to n -1 do begin lIndex:= Self.ReadVarInt(AStream, True); //slotindex nn:= Self.ReadVarInt(AStream, True); for j:= 0 to nn -1 do begin lTimelineType:= Self.ReadByte(AStream); lFrameCount:= Self.ReadVarInt(AStream, True); case lTimelineType of SLOT_ATTACHMENT: begin lAttachmentTimeline:= TAttachmentTimeline.Create(lFrameCount); lAttachmentTimeline.SlotIndex:= lIndex; for lFrameIndex:= 0 to lFrameCount -1 do lAttachmentTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream), Self.ReadString(AStream)); lAnimation.Timelines.Add(lAttachmentTimeline); lDuration:= System.Math.Max(lDuration, lAttachmentTimeline.Frames[lFrameCount-1]); end; SLOT_COLOR: begin lColorTimeline:= TColorTimeline.Create(lFrameCount); lColorTimeline.SlotIndex:= lIndex; for lFrameIndex:= 0 to lFrameCount -1 do begin lTime:= Self.ReadFloat(AStream); lColor:= Self.ReadInt(AStream); lR:= ((lColor and $ff) shr 24) / 255; lG:= ((lColor and $00ff) shr 16) / 255; lB:= ((lColor and $0000ff) shr 8) / 255; lA:= ((lColor and $000000ff)) / 255; lColorTimeline.SetFrame(lFrameIndex, lTime, lR, lG, lB, lA); if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lColorTimeline); end; lAnimation.Timelines.Add(lColorTimeline); lDuration:= System.Math.Max(lDuration, lColorTimeline.Frames[(lColorTimeline.FrameCount-1)*TColorTimeline.ENTRIES]); end; SLOT_TWO_COLOR: begin lTwoColorTimeline:= TTwoColorTimeline.Create(lFrameCount); lTwoColorTimeline.SlotIndex:= lIndex; for lFrameIndex:= 0 to lFrameCount -1 do begin lTime:= Self.ReadFloat(AStream); lColor:= Self.ReadInt(AStream); lR:= ((lColor and $ff) shr 24) / 255; lG:= ((lColor and $00ff) shr 16) / 255; lB:= ((lColor and $0000ff) shr 8) / 255; lA:= ((lColor and $000000ff)) / 255; lColor2:= Self.ReadInt(AStream); // 0x00rrggbb lR2:= ((lColor2 and $00ff) shr 16) / 255; lG2:= ((lColor2 and $0000ff) shr 8) / 255; lB2:= ((lColor2 and $000000ff)) / 255; lTwoColorTimeline.SetFrame(lFrameIndex, lTime, lR, lG, lB, lA, lR2, lG2, lB2); if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lTwoColorTimeline); end; lAnimation.Timelines.Add(lTwoColorTimeline); lDuration:= System.Math.Max(lDuration, lTwoColorTimeline.Frames[(lColorTimeline.FrameCount-1)*TTwoColorTimeline.ENTRIES]); end; end; end; end; // Bone timelines. n:= Self.ReadVarInt(AStream, True); for i:= 0 to n -1 do begin lIndex:= Self.ReadVarInt(AStream, True); //boneindex nn:= Self.ReadVarInt(AStream, True); for j:= 0 to nn -1 do begin lTimelineType:= Self.ReadByte(AStream); lFrameCount:= Self.ReadVarInt(AStream, True); case lTimelineType of BONE_ROTATE: begin lRotateTimeline:= TRotateTimeline.Create(lFrameCount); lRotateTimeline.BoneIndex:= lIndex; for lFrameIndex:= 0 to lFrameCount -1 do begin lRotateTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream), Self.ReadFloat(AStream)); if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lRotateTimeline); end; lAnimation.Timelines.Add(lRotateTimeline); lDuration:= System.Math.Max(lDuration, lRotateTimeline.Frames[(lFrameCount-1) * TRotateTimeline.ENTRIES]); end; BONE_TRANSLATE, BONE_SCALE, BONE_SHEAR: begin lTimelineScale:= 1; case lTimelineType of BONE_SCALE: lTranslateTimeline:= TScaleTimeline.Create(lFrameCount); BONE_SHEAR: lTranslateTimeline:= TShearTimeline.Create(lFrameCount); else begin lTranslateTimeline:= TTranslateTimeline.Create(lFrameCount); lTimelineScale:= Self.Scale; end; end; lTranslateTimeline.BoneIndex:= lIndex; for lFrameIndex:= 0 to lFrameCount -1 do begin lTranslateTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream), Self.ReadFloat(AStream) * lTimelineScale, Self.ReadFloat(AStream) * lTimelineScale); if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lTranslateTimeline); end; lAnimation.Timelines.Add(lTranslateTimeline); lDuration:= System.Math.Max(lDuration, lTranslateTimeline.Frames[(lFrameCount-1) * TTranslateTimeline.ENTRIES]); end; end; end; end; // IK timelines. n:= Self.ReadVarInt(AStream, True); for i:= 0 to n -1 do begin lIndex:= Self.ReadVarInt(AStream, True); //IkConstraintIndex lFrameCount:= Self.ReadVarInt(AStream, True); lIkConstraintTimeline:= TIkConstraintTimeline.Create(lFrameCount); lIkConstraintTimeline.IkConstraintIndex:= lIndex; for lFrameIndex:= 0 to lFrameCount -1 do begin lIkConstraintTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream), Self.ReadFloat(AStream), Self.ReadSByte(AStream)); if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lIkConstraintTimeline); end; lAnimation.Timelines.Add(lIkConstraintTimeline); lDuration:= System.Math.Max(lDuration, lIkConstraintTimeline.Frames[(lFrameCount-1) * TIkConstraintTimeline.ENTRIES]); end; // Transform constraint timelines. n:= Self.ReadVarInt(AStream, True); for i:= 0 to n -1 do begin lIndex:= Self.ReadVarInt(AStream, True); //TransformConstraintIndex lFrameCount:= Self.ReadVarInt(AStream, True); lTransformConstraintTimeline:= TTransformConstraintTimeline.Create(lFrameCount); lTransformConstraintTimeline.TransformConstraintIndex:= lIndex; for lFrameIndex:= 0 to lFrameCount -1 do begin lTransformConstraintTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream), Self.ReadFloat(AStream), Self.ReadFloat(AStream), Self.ReadFloat(AStream), Self.ReadFloat(AStream)); if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lTransformConstraintTimeline); end; lAnimation.Timelines.Add(lTransformConstraintTimeline); lDuration:= System.Math.Max(lDuration, lTransformConstraintTimeline.Frames[(lFrameCount-1) * TTransformConstraintTimeline.ENTRIES]); end; // Path constraint timelines. n:= Self.ReadVarInt(AStream, True); for i:= 0 to n -1 do begin lIndex:= Self.ReadVarInt(AStream, True); //PathConstraintIndex lPathConstraintData:= ASkeletonData.PathConstraintDatas.Items[lIndex]; nn:= Self.ReadVarInt(AStream, True); for j:= 0 to nn -1 do begin lTimelineType:= Self.ReadSByte(AStream); lFrameCount:= Self.ReadVarInt(AStream, True); case lTimelineType of PATH_POSITION, PATH_SPACING: begin lTimelineScale:= 1; if lTimelineType = PATH_SPACING then begin lPathConstraintPositionTimeline:= TPathConstraintSpacingTimeline.Create(lFrameCount); if (lPathConstraintData.SpacingMode = TSpacingMode.smLength) or (lPathConstraintData.SpacingMode = TSpacingMode.smFixed) then lTimelineScale:= Self.Scale; end else begin lPathConstraintPositionTimeline:= TPathConstraintPositionTimeline.Create(lFrameCount); if lPathConstraintData.PositionMode = TPositionMode.pmFixed then lTimelineScale:= Self.Scale; end; lPathConstraintPositionTimeline.PathConstraintIndex:= lIndex; for lFrameIndex:= 0 to lFrameCount -1 do begin lPathConstraintPositionTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream), Self.ReadFloat(AStream) * lTimelineScale); if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lPathConstraintPositionTimeline); end; lAnimation.Timelines.Add(lPathConstraintPositionTimeline); lDuration:= System.Math.Max(lDuration, lPathConstraintPositionTimeline.Frames[(lFrameCount-1) * TPathConstraintPositionTimeline.ENTRIES]); end; PATH_MIX: begin lPathConstraintMixTimeline:= TPathConstraintMixTimeline.Create(lFrameCount); lPathConstraintMixTimeline.PathConstraintIndex:= lIndex; for lFrameIndex:= 0 to lFrameCount -1 do begin lPathConstraintMixTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream), Self.ReadFloat(AStream), Self.ReadFloat(AStream)); if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lPathConstraintMixTimeline); end; lAnimation.Timelines.Add(lPathConstraintMixTimeline); lDuration:= System.Math.Max(lDuration, lPathConstraintMixTimeline.Frames[(lFrameCount-1) * TPathConstraintMixTimeline.ENTRIES]); end; end; end; end; // Deform timelines. n:= Self.ReadVarInt(AStream, True); for i:= 0 to n -1 do begin lSkin:= ASkeletonData.Skins.Items[Self.ReadVarInt(AStream, True)]; nn:= Self.ReadVarInt(AStream, True); for j:= 0 to nn -1 do begin lIndex:= Self.ReadVarInt(AStream, True); //slotindex nnn:= Self.ReadVarInt(AStream, True); for k:= 0 to nnn -1 do begin lVertexAttachment:= TVertexAttachment(lSkin.GetAttachment(lIndex, Self.ReadString(AStream))); lWeighted:= Length(lVertexAttachment.Bones) > 0; if lWeighted then lDeformLength:= System.Math.Floor(Length(lVertexAttachment.Vertices) / 3 *2) else lDeformLength:= Length(lVertexAttachment.Vertices); lFrameCount:= Self.ReadVarInt(AStream, True); lDeformTiemline:= TDeformTimeline.Create(lFrameCount); lDeformTiemline.SlotIndex:= lIndex; lDeformTiemline.Attachment:= lVertexAttachment; for lFrameIndex:= 0 to lFrameCount -1 do begin lTime:= Self.ReadFloat(AStream); lEnd:= Self.ReadVarInt(AStream, True); if lEnd = 0 then begin if lWeighted then SetLength(lDeform, lDeformLength) else lDeform:= lVertexAttachment.Vertices; end else begin SetLength(lDeform, lDeformLength); lStart:= Self.ReadVarInt(AStream, True); lEnd:= lEnd + lStart; if Self.Scale = 1 then begin for v:= lStart to lEnd -1 do lDeform[v]:= Self.ReadFloat(AStream); end else begin for v:= lStart to lEnd -1 do lDeform[v]:= Self.ReadFloat(AStream) * Self.Scale; end; if not lWeighted then for v:= 0 to Length(lDeform) -1 do lDeform[v]:= lDeform[v] + lVertexAttachment.Vertices[v]; end; lDeformTiemline.SetFrame(lFrameIndex, lTime, lDeform); if lFrameIndex < lFrameCount -1 then Self.ReadCurve(AStream, lFrameIndex, lDeformTiemline); end; lAnimation.Timelines.Add(lDeformTiemline); lDuration:= System.Math.Max(lDuration, lDeformTiemline.Frames[lFrameCount -1]); end; end; end; // Draw order timeline. n:= Self.ReadVarInt(AStream, True); //drawOrderCount if n > 0 then begin lDrawOrderTimeline:= TDrawOrderTimeline.Create(n); lSlotCount:= ASkeletonData.SlotDatas.Count; for i:= 0 to n -1 do begin lTime:= Self.ReadFloat(AStream); lOffsetCount:= Self.ReadVarInt(AStream, True); SetLength(lDrawOrder, lSlotCount); for j:= lSlotCount -1 downto 0 do lDrawOrder[j]:= -1; SetLength(lUnChanged, lSlotCount - lOffsetCount); lOriginalIndex:= 0; lUnChangedIndex:= 0; for j:= 0 to lOffsetCount -1 do begin lIndex:= Self.ReadVarInt(AStream, True); //slotindex // Collect unchanged items. while lOriginalIndex <> lIndex do begin lUnChanged[lUnChangedIndex]:= lOriginalIndex; Inc(lUnChangedIndex); Inc(lOriginalIndex); end; lDrawOrder[lOriginalIndex+Self.ReadVarInt(AStream, True)]:= lOriginalIndex; Inc(lOriginalIndex); end; // Collect remaining unchanged items. while lOriginalIndex < lSlotCount do begin lUnChanged[lUnChangedIndex]:= lOriginalIndex; Inc(lUnChangedIndex); Inc(lOriginalIndex); end; // Fill in unchanged items. for j:= lSlotCount downto 0 do begin if lDrawOrder[j] = -1 then begin Dec(lUnChangedIndex); lDrawOrder[j]:= lUnChanged[lUnChangedIndex]; end; end; lDrawOrderTimeline.SetFrame(i, lTime, lDrawOrder); end; lAnimation.Timelines.Add(lDrawOrderTimeline); lDuration:= System.Math.Max(lDuration, lDrawOrderTimeline.Frames[lFrameCount -1]); end; // Event timeline. n:= Self.ReadVarInt(AStream, True); //eventCount if n > 0 then begin lEventTimeline:= TEventTimeline.Create(n); for i:= 0 to n -1 do begin lTime:= Self.ReadFloat(AStream); lEventData:= ASkeletonData.EventDatas.Items[Self.ReadVarInt(AStream, True)]; lEvent:= TSpineEvent.Create(lTime, lEventData); lEvent.IntValue:= Self.ReadVarInt(AStream, False); lEvent.FloatValue:= Self.ReadFloat(AStream); if Self.ReadBoolean(AStream) then lEvent.StringValue:= Self.ReadString(AStream) else lEvent.StringValue:= lEventData.StringValue; lEventTimeline.SetFrame(i, lEvent); end; lAnimation.Timelines.Add(lEventTimeline); lDuration:= System.Math.Max(lDuration, lEventTimeline.Frames[lFrameCount -1]); end; lAnimation.Timelines.TrimExcess; lAnimation.Duration:= lDuration; ASkeletonData.Animations.Add(lAnimation); except lAnimation.Free; raise; end; end; procedure TSpineSkeletonBinary.ReadCurve(const AStream: TStream; const AFrameIndex: Integer; const ATimeline: TCurveTimeline); begin case Self.ReadByte(AStream) of CURVE_STEPPED: ATimeline.SetStepped(AFrameIndex); CURVE_BEZIER: ATimeline.SetCurve(AFrameIndex, Self.ReadFloat(AStream), Self.ReadFloat(AStream), Self.ReadFloat(AStream), Self.ReadFloat(AStream)); end; end; function TSpineSkeletonBinary.ReadSByte(const AStream: TStream): SByte; var lValue: Integer; begin lValue:= Self.ReadByte(AStream); if lValue = -1 then raise Exception.Create('end of stream.'); result:= lValue; end; function TSpineSkeletonBinary.ReadBoolean(const AStream: TStream): Boolean; begin result:= Self.ReadByte(AStream) <> 0; end; function TSpineSkeletonBinary.ReadFloat(const AStream: TStream): Single; begin FBuffer[3]:= Self.ReadByte(AStream); FBuffer[2]:= Self.ReadByte(AStream); FBuffer[1]:= Self.ReadByte(AStream); FBuffer[0]:= Self.ReadByte(AStream); result:= PSingle(@FBuffer[0])^; end; function TSpineSkeletonBinary.ReadInt(const AStream: TStream): Integer; begin result:= Self.ReadByte(AStream) shl 24; result:= result + (Self.ReadByte(AStream) shl 16); result:= result + (Self.ReadByte(AStream) shl 8); result:= result + Self.ReadByte(AStream); end; function TSpineSkeletonBinary.ReadVarInt(const AStream: TStream; const AOptimizePositive: Boolean): Integer; var lByte: Integer; begin lByte:= Self.ReadByte(AStream); result:= lByte and $7F; if (lByte and $80) <> 0 then begin lByte:= Self.ReadByte(AStream); result:= result or ((lByte and $7F) shl 7); if (lByte and $80) <> 0 then begin lByte:= Self.ReadByte(AStream); result:= result or ((lByte and $7F) shl 14); if (lByte and $80) <> 0 then begin lByte:= Self.ReadByte(AStream); result:= result or ((lByte and $7F) shl 21); if (lByte and $80) <> 0 then result:= result or ((lByte and $7F) shl 28); end; end; end; // if not AOptimizePositive then result:= (((Result shr 1) and $7fffffff) xor -(Result and 1)); end; function TSpineSkeletonBinary.ReadString(const AStream: TStream): string; var lByteCount: Integer; lBuffer: TArray<Byte>; begin lByteCount:= Self.ReadVarInt(AStream, True); case lByteCount of 0, 1: exit(''); else begin lByteCount:= lByteCount - 1; if Length(lBuffer) < lByteCount then SetLength(lBuffer, lByteCount) else TArray.Copy<Byte>(FBuffer, lBuffer, 0, 0, Length(FBuffer)); TSpineSkeletonBinary.ReadFully(AStream, lBuffer, 0, lByteCount); result:= System.SysUtils.StringOf(lBuffer); end; end; end; class procedure TSpineSkeletonBinary.ReadFully(const AStream: TStream; var ABuffer: TArray<Byte>; const AOffset: Integer; var ALength: Integer); var lOffset, lCount: Integer; begin lOffset:= AOffset; while ALength > 0 do begin lCount:= AStream.Read(ABuffer, lOffset, ALength); if lCount <= 0 then raise Exception.Create('end of stream.'); lOffset:= lOffset + lCount; ALength:= ALength - lCount; end; end; end.
上一篇翻译了图集解析单元,今天把骨架解析也翻译完了(二进制,.skel文件)。1000多行代码,把我累的。。。
spine.core.bone, spine.core.slot, spine.core.skin,
spine.core.attachment, spine.core.constraint, spine.core.skeleton,
spine.core.animation.timeline, spine.core.skeleton.json, spine.core.event,
spine.core.animation
上面这些依赖的单元部分还在整理,慢慢放上来。
到现在为止,算是把基本数据的解析工作完成了。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- Delphi Format、FormatFloat与FormatDateTime的用法 2020-05-22
- Delphi-基础(for循环) 2019-12-02
- BussinessSkinForm 入门教程 2019-08-16
- Delphi BusinessSkinForm使用说明 2019-08-16
- webform 简单的服务器控件。 2018-06-27
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash