UINT OBSAPIInterface::CreateHotkey(DWORD hotkey, OBSHOTKEYPROC hotkeyProc, UPARAM param) { if(!hotkey) return 0; //FIXME: vk and fsModifiers aren't used? DWORD vk = LOBYTE(hotkey); DWORD modifier = HIBYTE(hotkey); DWORD fsModifiers = 0; if(modifier & HOTKEYF_ALT) fsModifiers |= MOD_ALT; if(modifier & HOTKEYF_CONTROL) fsModifiers |= MOD_CONTROL; if(modifier & HOTKEYF_SHIFT) fsModifiers |= MOD_SHIFT; OSEnterMutex(App->hHotkeyMutex); HotkeyInfo &hi = *hotkeys.CreateNew(); hi.hotkeyID = ++curHotkeyIDVal; hi.hotkey = hotkey; hi.hotkeyProc = hotkeyProc; hi.param = param; hi.bModifiersDown = false; hi.bHotkeyDown = false; OSLeaveMutex(App->hHotkeyMutex); return curHotkeyIDVal; }
void SendPacket(BYTE *data, UINT size, DWORD timestamp, PacketType type) { ProcessDelayedPackets(timestamp); NetworkPacket *newPacket = delayedPackets.CreateNew(); newPacket->data.CopyArray(data, size); newPacket->timestamp = timestamp; newPacket->type = type; lastTimestamp = timestamp; }
void MergeProfileInfo(ProfileNodeInfo *info, DWORD rootLastCall, DWORD updatedLastCall) { bSingular = info->bSingular; numCalls += info->numCalls; if(lastCall == rootLastCall) lastCall = updatedLastCall; totalTimeElapsed += info->totalTimeElapsed; lastTimeElapsed = info->lastTimeElapsed; cpuTimeElapsed += info->cpuTimeElapsed; lastCpuTimeElapsed = info->lastCpuTimeElapsed; numParallelCalls = info->numParallelCalls; for(UINT i = 0; i < info->Children.Num(); i++) { ProfileNodeInfo &child = info->Children[i]; ProfileNodeInfo *sumChild = FindSubProfile(child.lpName); if(!sumChild) { sumChild = Children.CreateNew(); sumChild->lpName = child.lpName; } sumChild->MergeProfileInfo(&child, rootLastCall, updatedLastCall); } }
void SkinMesh::LoadAnimations(CTSTR lpName) { traceIn(SkinMesh::LoadAnimations); assert(lpName); DWORD i, j, k; String strMeshPath; if(!Engine::ConvertResourceName(lpName, TEXT("models"), strMeshPath)) return; String strFilePath; strFilePath << GetPathWithoutExtension(strMeshPath) << TEXT(".xan"); //----------------------------------------------- XFileInputSerializer animData; if(!animData.Open(strFilePath)) { AppWarning(TEXT("Could not open animation file \"%s\""), (TSTR)strFilePath); return; } DWORD ver; animData << ver; if(ver != ANIMFILE_VER && ver != 0x100) { AppWarning(TEXT("'%s': Bad animation file version"), strFilePath); animData.Close(); return; } //----------------------------------------------- unsigned int numSequences; animData << numSequences; SequenceList.SetSize(numSequences); for(i=0; i<numSequences; i++) animData << SequenceList[i]; DWORD nBones; animData << nBones; BoneList.SetSize(nBones); for(i=0; i<nBones; i++) { Bone &bone = BoneList[i]; List<DWORD> RigidVerts; List<VWeight> BlendedVerts; DWORD nRigid, nBlended; //---------------------------- animData << bone.Pos << bone.Rot << nRigid << nBlended << bone.flags << bone.idParent; animData << RigidVerts; animData << BlendedVerts; if(bone.idParent != 0xFFFFFFFF) { bone.Parent = BoneList+bone.idParent; bone.LocalPos = bone.Pos - BoneList[bone.idParent].Pos; BoneList[bone.idParent].Children << &bone; } else { bone.Parent = NULL; bone.LocalPos = bone.Pos; bone.flags |= BONE_ROOT; } //---------------------------- bone.Weights.SetSize(nRigid); for(j=0; j<RigidVerts.Num(); j++) { VWeight &weight = bone.Weights[j]; weight.vert = RigidVerts[j]; weight.weight = 1.0f; } bone.Weights.AppendList(BlendedVerts); //---------------------------- DWORD nAnim; animData << nAnim; bone.seqKeys.SetSize(nAnim); for(j=0; j<nAnim; j++) { SeqKeys &keys = bone.seqKeys[j]; DWORD nPosKeys, nRotKeys; //---------------------------- animData << nRotKeys; keys.hasRotKeys = nRotKeys > 0; if(keys.hasRotKeys) { keys.lpRotKeys = (Quat*)Allocate(nRotKeys*sizeof(Quat)); animData.Serialize(keys.lpRotKeys, nRotKeys*sizeof(Quat)); keys.lpRotTans = (Quat*)Allocate(nRotKeys*sizeof(Quat)); for(k=0; k<nRotKeys; k++) { DWORD kp1 = (k == (nRotKeys-1)) ? k : (k+1); DWORD km1 = (k == 0) ? 0 : k-1; Quat &qk = keys.lpRotKeys[k]; Quat &qkp1 = keys.lpRotKeys[kp1]; Quat &qkm1 = keys.lpRotKeys[km1]; keys.lpRotTans[k] = qk.GetInterpolationTangent(qkm1, qkp1); } } //---------------------------- animData << nPosKeys; keys.hasPosKeys = nPosKeys > 0; if(keys.hasPosKeys) { keys.lpPosKeys = (Vect*)Allocate(nPosKeys*sizeof(Vect)); Vect::SerializeArray(animData, keys.lpPosKeys, nPosKeys); keys.lpPosTans = (Vect*)Allocate(nPosKeys*sizeof(Vect)); for(k=0; k<nPosKeys; k++) { DWORD kp1 = (k == (nPosKeys-1)) ? k : (k+1); DWORD km1 = (k == 0) ? 0 : k-1; Vect &pk = keys.lpPosKeys[k]; Vect &pkp1 = keys.lpPosKeys[kp1]; Vect &pkm1 = keys.lpPosKeys[km1]; keys.lpPosTans[k] = pk.GetInterpolationTangent(pkm1, pkp1); } } } } //----------------------------------------------- int num; animData << num; BoneExtensions.SetSize(num); BoneExtensionNames.SetSize(num); for(int i=0; i<num; i++) { animData << BoneExtensionNames[i]; animData << BoneExtensions[i]; } //----------------------------------------------- AnimatedSections.SetSize(nSections); if(ver == 0x100) //remove { UINT *indices = (UINT*)IdxBuffer->GetData(); List<UINT> adjustedIndices; adjustedIndices.CopyArray(indices, IdxBuffer->NumIndices()); VBData *vbd = VertBuffer->GetData(); List<Vect> &verts = vbd->VertList; //--------- // get vert data List<VertAnimInfo> vertInfo; vertInfo.SetSize(nVerts); for(int i=0; i<nBones; i++) { Bone &bone = BoneList[i]; for(int j=0; j<bone.Weights.Num(); j++) { VWeight &vertWeight = bone.Weights[j]; if(!vertInfo[vertWeight.vert].bones.HasValue(i)) { vertInfo[vertWeight.vert].bones << i; vertInfo[vertWeight.vert].weights << vertWeight.weight; } } } //--------- // remove excess bone influence from verts (let just set the max to 3 for this) /*for(int i=0; i<vertInfo.Num(); i++) { VertAnimInfo &vert = vertInfo[i]; while(vert.bones.Num() > 3) { float weakestWeight = M_INFINITE; UINT weakestID; for(int j=0; j<vert.bones.Num(); j++) { if(vert.weights[j] < weakestWeight) { weakestID = j; weakestWeight = vert.weights[j]; } } float weightAdjust = 1.0f/(1.0f-weakestWeight); vert.weights.Remove(weakestID); vert.bones.Remove(weakestID); for(int j=0; j<vert.weights.Num(); j++) vert.weights[j] *= weightAdjust; } }*/ for(int i=0; i<vertInfo.Num(); i++) { VertAnimInfo &vert = vertInfo[i]; for(int j=0; j<vert.bones.Num(); j++) { if(vert.weights[j] <= 0.15f) { float weightAdjust = 1.0f/(1.0f-vert.weights[j]); vert.weights.Remove(j); vert.bones.Remove(j); for(int k=0; k<vert.weights.Num(); k++) vert.weights[k] *= weightAdjust; --j; } } } //--------- // remove excess bone influence from tris (can only have 4 bones influencing any triangle) for(int i=0; i<nSections; i++) { DrawSection §ion = SectionList[i]; if(!section.numFaces) continue; for(int j=0; j<section.numFaces; j++) { UINT *triVertIDs = &indices[(section.startFace+j)*3]; List<UINT> bones; List<float> bestVertWeights; for(int k=0; k<3; k++) { VertAnimInfo &info = vertInfo[triVertIDs[k]]; for(int l=0; l<info.bones.Num(); l++) { UINT id = bones.FindValueIndex(info.bones[l]); if(id == INVALID) { bones.Add(info.bones[l]); bestVertWeights.Add(info.weights[l]); } else bestVertWeights[id] = MAX(bestVertWeights[id], info.weights[l]); } } while(bones.Num() > 4) { int removeBone, removeBoneID; float bestWeight = M_INFINITE; for(int k=0; k<bones.Num(); k++) { if(bestVertWeights[k] < bestWeight) { removeBone = bones[k]; removeBoneID = k; bestWeight = bestVertWeights[k]; } } for(int k=0; k<3; k++) { VertAnimInfo &info = vertInfo[triVertIDs[k]]; UINT id = info.bones.FindValueIndex(removeBone); if(id == INVALID) continue; float weightAdjust = 1.0f/(1.0f-info.weights[id]); info.weights.Remove(id); info.bones.Remove(id); for(int l=0; l<info.weights.Num(); l++) info.weights[l] *= weightAdjust; } bones.Remove(removeBoneID); bestVertWeights.Remove(removeBoneID); } } } //--------- // sort out sections of triangles that are influenced up to a max of 4 bones // also, duplicate shared verts VBData *newVBD = new VBData; newVBD->CopyList(*vbd); newVBD->TVList.SetSize(2); newVBD->TVList[1].SetWidth(4); newVBD->TVList[1].SetSize(nVerts); List<SubSectionInfo> newSubSections; for(int i=0; i<nSections; i++) { List<TriBoneInfo> triInfo; DrawSection §ion = SectionList[i]; if(!section.numFaces) continue; for(int j=0; j<section.numFaces; j++) { UINT *triVertIDs = &indices[(section.startFace+j)*3]; TriBoneInfo &newTri = *triInfo.CreateNew(); for(int k=0; k<3; k++) { VertAnimInfo &info = vertInfo[triVertIDs[k]]; for(int l=0; l<info.bones.Num(); l++) newTri.bones.SafeAdd(info.bones[l]); } } BitList UsedTris; UsedTris.SetSize(section.numFaces); DWORD nUsedTris = 0; while(nUsedTris != section.numFaces) { DWORD triSectionID = newSubSections.Num(); SubSectionInfo *curSubSecInfo = newSubSections.CreateNew(); curSubSecInfo->section = i; for(int j=0; j<triInfo.Num(); j++) { if(UsedTris[j]) continue; TriBoneInfo &tri = triInfo[j]; List<UINT> secBones; secBones.CopyList(curSubSecInfo->bones); BOOL bBadTri = FALSE; for(int k=0; k<tri.bones.Num(); k++) { secBones.SafeAdd(tri.bones[k]); if(secBones.Num() > 4) break; } if(secBones.Num() > 4) continue; DWORD triID = section.startFace+j; curSubSecInfo->bones.CopyList(secBones); curSubSecInfo->tris << triID; UINT *triVertIDs = &indices[triID*3]; for(int k=0; k<3; k++) { VertAnimInfo *info = &vertInfo[triVertIDs[k]]; UINT id = info->sections.FindValueIndex(triSectionID); if(id == INVALID) { UINT vertID; if(info->sections.Num() >= 1) //duplicate vertex { vertID = newVBD->DuplicateVertex(triVertIDs[k]); adjustedIndices[(triID*3)+k] = vertID; VertAnimInfo &newVertInfo = *vertInfo.CreateNew(); info = &vertInfo[triVertIDs[k]]; //reset pointer newVertInfo.bones.CopyList(info->bones); newVertInfo.weights.CopyList(info->weights); newVertInfo.sections << triSectionID; } else vertID = triVertIDs[k]; info->sections << triSectionID; info->sectionVertIDs << vertID; List<Vect4> &vertWeights = *newVBD->TVList[1].GetV4(); for(int l=0; l<4; l++) { if(l >= curSubSecInfo->bones.Num()) vertWeights[vertID].ptr[l] = 0.0f; else { UINT boneID = curSubSecInfo->bones[l]; UINT weightID = info->bones.FindValueIndex(boneID); if(weightID == INVALID) vertWeights[vertID].ptr[l] = 0.0f; else vertWeights[vertID].ptr[l] = info->weights[weightID]; } } } else adjustedIndices[(triID*3)+k] = info->sectionVertIDs[id]; } UsedTris.Set(j); ++nUsedTris; } } for(int j=0; j<triInfo.Num(); j++) triInfo[j].FreeData(); } //--------- // create animated draw sections and create new index buffer DWORD curTriID = 0; List<UINT> newIndices; for(int i=0; i<newSubSections.Num(); i++) { SubSectionInfo &subSecInfo = newSubSections[i]; AnimSection &animSection = AnimatedSections[subSecInfo.section]; AnimSubSection &subSection = *animSection.SubSections.CreateNew(); subSection.numBones = subSecInfo.bones.Num(); for(int j=0; j<subSecInfo.bones.Num(); j++) subSection.bones[j] = subSecInfo.bones[j]; subSection.startFace = curTriID; for(int j=0; j<subSecInfo.tris.Num(); j++) { UINT *tri = &adjustedIndices[subSecInfo.tris[j]*3]; newIndices << tri[0] << tri[1] << tri[2]; ++curTriID; } subSection.numFaces = curTriID-subSection.startFace; subSecInfo.FreeData(); } //rebuild original bone data for(int i=0; i<BoneList.Num(); i++) BoneList[i].Weights.Clear(); for(int i=0; i<vertInfo.Num(); i++) { for(int j=0; j<vertInfo[i].bones.Num(); j++) { VWeight weight; weight.vert = i; weight.weight = vertInfo[i].weights[j]; BoneList[vertInfo[i].bones[j]].Weights << weight; } vertInfo[i].FreeData(); } delete VertBuffer; delete IdxBuffer; UINT numIndices; UINT *indexArray; newIndices.TransferTo(indexArray, numIndices); nVerts = newVBD->VertList.Num(); IdxBuffer = CreateIndexBuffer(GS_UNSIGNED_LONG, indexArray, numIndices); VertBuffer = CreateVertexBuffer(newVBD); } else { for(int i=0; i<nSections; i++) animData << AnimatedSections[i].SubSections; } animData.Close(); traceOut; }
bool Encode(LPVOID picInPtr, List<DataPacket> &packets, List<PacketType> &packetTypes, DWORD outputTimestamp) { x264_picture_t *picIn = (x264_picture_t*)picInPtr; x264_nal_t *nalOut; int nalNum; packets.Clear(); ClearPackets(); if(bRequestKeyframe && picIn) picIn->i_type = X264_TYPE_IDR; if(x264_encoder_encode(x264, &nalOut, &nalNum, picIn, &picOut) < 0) { AppWarning(TEXT("x264 encode failed")); return false; } if(bRequestKeyframe && picIn) { picIn->i_type = X264_TYPE_AUTO; bRequestKeyframe = false; } if(!bFirstFrameProcessed && nalNum) { delayOffset = -picOut.i_dts; bFirstFrameProcessed = true; } INT64 ts = INT64(outputTimestamp); int timeOffset; //if frame duplication is being used, the shift will be insignificant, so just don't bother adjusting audio timeOffset = int(picOut.i_pts-picOut.i_dts); timeOffset += frameShift; if(nalNum && timeOffset < 0) { frameShift -= timeOffset; timeOffset = 0; } //Log(TEXT("inpts: %005d, dts: %005d, pts: %005d, timestamp: %005d, offset: %005d, newoffset: %005d"), picIn->i_pts, picOut.i_dts, picOut.i_pts, outputTimestamp, timeOffset, picOut.i_pts-picOut.i_dts); timeOffset = htonl(timeOffset); BYTE *timeOffsetAddr = ((BYTE*)&timeOffset)+1; VideoPacket *newPacket = NULL; PacketType bestType = PacketType_VideoDisposable; bool bFoundFrame = false; for(int i=0; i<nalNum; i++) { x264_nal_t &nal = nalOut[i]; if(nal.i_type == NAL_SEI) { BYTE *skip = nal.p_payload; while(*(skip++) != 0x1); int skipBytes = (int)(skip-nal.p_payload); int newPayloadSize = (nal.i_payload-skipBytes); if (nal.p_payload[skipBytes+1] == 0x5) { SEIData.Clear(); BufferOutputSerializer packetOut(SEIData); packetOut.OutputDword(htonl(newPayloadSize)); packetOut.Serialize(nal.p_payload+skipBytes, newPayloadSize); } else { if (!newPacket) newPacket = CurrentPackets.CreateNew(); BufferOutputSerializer packetOut(newPacket->Packet); packetOut.OutputDword(htonl(newPayloadSize)); packetOut.Serialize(nal.p_payload+skipBytes, newPayloadSize); } } else if(nal.i_type == NAL_FILLER) { BYTE *skip = nal.p_payload; while(*(skip++) != 0x1); int skipBytes = (int)(skip-nal.p_payload); int newPayloadSize = (nal.i_payload-skipBytes); if (!newPacket) newPacket = CurrentPackets.CreateNew(); BufferOutputSerializer packetOut(newPacket->Packet); packetOut.OutputDword(htonl(newPayloadSize)); packetOut.Serialize(nal.p_payload+skipBytes, newPayloadSize); } else if(nal.i_type == NAL_SLICE_IDR || nal.i_type == NAL_SLICE) { BYTE *skip = nal.p_payload; while(*(skip++) != 0x1); int skipBytes = (int)(skip-nal.p_payload); if (!newPacket) newPacket = CurrentPackets.CreateNew(); if (!bFoundFrame) { newPacket->Packet.Insert(0, (nal.i_type == NAL_SLICE_IDR) ? 0x17 : 0x27); newPacket->Packet.Insert(1, 1); newPacket->Packet.InsertArray(2, timeOffsetAddr, 3); bFoundFrame = true; } int newPayloadSize = (nal.i_payload-skipBytes); BufferOutputSerializer packetOut(newPacket->Packet); packetOut.OutputDword(htonl(newPayloadSize)); packetOut.Serialize(nal.p_payload+skipBytes, newPayloadSize); switch(nal.i_ref_idc) { case NAL_PRIORITY_DISPOSABLE: bestType = MAX(bestType, PacketType_VideoDisposable); break; case NAL_PRIORITY_LOW: bestType = MAX(bestType, PacketType_VideoLow); break; case NAL_PRIORITY_HIGH: bestType = MAX(bestType, PacketType_VideoHigh); break; case NAL_PRIORITY_HIGHEST: bestType = MAX(bestType, PacketType_VideoHighest); break; } } /*else if(nal.i_type == NAL_SPS) { VideoPacket *newPacket = CurrentPackets.CreateNew(); BufferOutputSerializer headerOut(newPacket->Packet); headerOut.OutputByte(0x17); headerOut.OutputByte(0); headerOut.Serialize(timeOffsetAddr, 3); headerOut.OutputByte(1); headerOut.Serialize(nal.p_payload+5, 3); headerOut.OutputByte(0xff); headerOut.OutputByte(0xe1); headerOut.OutputWord(htons(nal.i_payload-4)); headerOut.Serialize(nal.p_payload+4, nal.i_payload-4); x264_nal_t &pps = nalOut[i+1]; //the PPS always comes after the SPS headerOut.OutputByte(1); headerOut.OutputWord(htons(pps.i_payload-4)); headerOut.Serialize(pps.p_payload+4, pps.i_payload-4); }*/ else continue; } packetTypes << bestType; packets.SetSize(CurrentPackets.Num()); for(UINT i=0; i<packets.Num(); i++) { packets[i].lpPacket = CurrentPackets[i].Packet.Array(); packets[i].size = CurrentPackets[i].Packet.Num(); } return true; }
void EditorMesh::BuildLightmapUVs(float maxAngle, float adjVal, int seperationUnits) { Vect maxSize = bounds.Max-bounds.Min; //adjVal *= MAX(maxSize.x, MAX(maxSize.y, maxSize.z)); adjVal *= bounds.GetDiamater(); int i, j; MakePolyEdges(); maxAngle = cosf(RAD(maxAngle)); BitList ProcessedPolys, ProcessedEdges, ProcessedVerts; List<UINT> UnprocessedPolys; ProcessedPolys.SetSize(PolyList.Num()); ProcessedEdges.SetSize(PolyEdgeList.Num()); ProcessedVerts.SetSize(VertList.Num()); UnprocessedPolys.SetSize(PolyList.Num()); for(i=0; i<UnprocessedPolys.Num(); i++) UnprocessedPolys[i] = i; List<UVSection> Sections; BitList SplitVerts; SplitVerts.SetSize(VertList.Num()); //go through each polygon and find neighboring polygons while(UnprocessedPolys.Num()) { UINT curPoly = UnprocessedPolys[0]; List<UINT> SectionPolys; UVSection §ion = *Sections.CreateNew(); section.mapDir = GetPolyDir(curPoly); List<Vect> PolyLines; // calling this recursively will build us up a section based upon this poly AddLMPolyInfo info(curPoly, maxAngle, section.mapDir, SectionPolys, PolyLines, UnprocessedPolys, ProcessedPolys, ProcessedEdges); AddLightmapUVPoly(info); section.mapDir.Norm(); //find best mapping direction Vect XDir, YDir; BitList addedLines; addedLines.SetSize(PolyLines.Num()); float bestLength = 0.0f; Vect bestDir; for(i=0; i<PolyLines.Num(); i++) { if(addedLines[i]) continue; addedLines.Set(i); Vect &line1 = PolyLines[i]; Vect line1Norm = (line1 + (section.mapDir * -section.mapDir.Dot(line1))).Norm(); Vect lineTotal = line1; for(j=i+1; j<PolyLines.Num(); j++) { if(addedLines[j]) continue; Vect &line2 = PolyLines[j]; Vect line2Norm = (line2 + (section.mapDir * -section.mapDir.Dot(line2))).Norm(); if(line1Norm.Dot(line2Norm) > 0.9f) //about 10 degrees { lineTotal += line2; addedLines.Set(j); } } float length = lineTotal.Len(); if(length > bestLength) { bestDir = lineTotal.Norm(); bestLength = length; } } if(section.mapDir.CloseTo(bestDir) || (-section.mapDir).CloseTo(bestDir)) { if(bestDir.x > bestDir.y) { if(bestDir.x > bestDir.z) *(DWORD*)&bestDir.x |= 0x80000000; else *(DWORD*)&bestDir.z |= 0x80000000; } else { if(bestDir.y > bestDir.z) *(DWORD*)&bestDir.y |= 0x80000000; else *(DWORD*)&bestDir.z |= 0x80000000; } } YDir = bestDir.Cross(section.mapDir).Norm(); XDir = YDir.Cross(section.mapDir).Norm(); /*if(section.mapDir.GetAbs().CloseTo(Vect(0.0f, 1.0f, 0.0f))) { float fOne = (*(DWORD*)§ion.mapDir.y & 0x80000000) ? -1.0f : 1.0f; XDir.Set(fOne, 0.0f, 0.0f); YDir.Set(0.0f, 0.0f, fOne); } else { XDir = Vect(0.0f, 1.0f, 0.0f).Cross(section.mapDir).Norm(); YDir = XDir.Cross(section.mapDir).Norm(); }*/ float top, bottom, left, right; //project UVs onto this section for(i=0; i<SectionPolys.Num(); i++) { PolyFace &poly = PolyList[SectionPolys[i]]; for(j=0; j<poly.Faces.Num(); j++) { UINT faceID = poly.Faces[j]; Face &f = FaceList[faceID]; section.Faces << faceID; for(int k=0; k<3; k++) { UINT vert = f.ptr[k]; Vect &v = VertList[vert]; Vect2 uv; uv.x = XDir.Dot(v); uv.y = YDir.Dot(v); if(!section.ProjectedVerts.Num()) { left = right = uv.x; top = bottom = uv.y; } else { if(uv.x < left) left = uv.x; else if(uv.x > right) right = uv.x; if(uv.y < top) top = uv.y; else if(uv.y > bottom) bottom = uv.y; } section.Verts << vert; section.ProjectedVerts << uv; } } } section.width = (right-left)+(adjVal*2.0f); section.height = (bottom-top)+(adjVal*2.0f); for(i=0; i<section.Verts.Num(); i++) { section.ProjectedVerts[i].x -= left-adjVal; section.ProjectedVerts[i].y -= top-adjVal; } //find any verts that might need splitting section.SplitVerts.SetSize(section.Verts.Num()); for(i=0; i<section.Verts.Num(); i++) { BOOL bFoundVert = FALSE; UINT vert = section.Verts[i]; if(SplitVerts[vert]) bFoundVert = TRUE; else { for(j=0; j<Sections.Num()-1; j++) { UVSection &prevSection = Sections[j]; if(prevSection.Verts.HasValue(vert)) { SplitVerts.Set(vert); bFoundVert = TRUE; break; } } } if(bFoundVert) section.SplitVerts.Set(i); } } //split verts where the lightmap coordinates are for(i=1; i<Sections.Num(); i++) { UVSection §ion = Sections[i]; for(j=0; j<section.SplitVerts.Num(); j++) { if(section.SplitVerts[j]) { UINT faceID = j/3; UINT faceVert = j%3; UINT vertID = section.Verts[j]; UINT newVertID = VertList.Num(); Face &f = FaceList[faceID]; f.ptr[faceVert] = newVertID; VertList.Add(VertList[vertID]); UVList.Add(UVList[vertID]); NormalList.Add(NormalList[vertID]); if(TangentList.Num()) TangentList.Add(TangentList[vertID]); } } } LMUVList.SetSize(VertList.Num()); //find the most optimal way to put sections together BestStorageInfo info(Sections, seperationUnits); FindBestUVStorage(info); float curSizeI = 1.0f/MAX(info.curSize.x, info.curSize.y); for(i=0; i<info.CurStorage.Num(); i++) { StorageData &data = info.CurStorage[i]; UVSection §ion = Sections[data.item]; for(j=0; j<section.Verts.Num(); j++) { UINT vertID = section.Verts[j]; if(ProcessedVerts[vertID]) continue; Vect2 &v = section.ProjectedVerts[j]; Vect2 newPos; if(data.bRot) { newPos.x = section.height-v.y; newPos.y = v.x; } else newPos = v; LMUVList[vertID] = (data.pos+newPos)*curSizeI; ProcessedVerts.Set(vertID); } } //free data for(i=0; i<Sections.Num(); i++) Sections[i].FreeData(); Sections.Clear(); FreePolyEdges(); }
UINT MMDeviceAudioSource::GetNextBuffer(float curVolume) { UINT captureSize = 0; HRESULT err = mmCapture->GetNextPacketSize(&captureSize); if(FAILED(err)) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetNextPacketSize failed")); return NoAudioAvailable; } float *outputBuffer = NULL; if(captureSize) { LPBYTE captureBuffer; DWORD dwFlags = 0; UINT numAudioFrames = 0; UINT64 devPosition; UINT64 qpcTimestamp; err = mmCapture->GetBuffer(&captureBuffer, &numAudioFrames, &dwFlags, &devPosition, &qpcTimestamp); if(FAILED(err)) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetBuffer failed")); return NoAudioAvailable; } QWORD newTimestamp; if(dwFlags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: woa woa woa, getting timestamp errors from the audio subsystem. device = %s"), GetDeviceName().Array()); if(!bBrokenTimestamp) newTimestamp = lastUsedTimestamp + numAudioFrames*1000/inputSamplesPerSec; } else { if(!bBrokenTimestamp) newTimestamp = qpcTimestamp/10000; /*UINT64 freq; mmClock->GetFrequency(&freq); Log(TEXT("position: %llu, numAudioFrames: %u, freq: %llu, newTimestamp: %llu, test: %llu"), devPosition, numAudioFrames, freq, newTimestamp, devPosition*8000/freq);*/ } //have to do this crap to account for broken devices or device drivers. absolutely unbelievable. if(!bFirstFrameReceived) { LARGE_INTEGER clockFreq; QueryPerformanceFrequency(&clockFreq); QWORD curTime = GetQPCTimeMS(clockFreq.QuadPart); if(newTimestamp < (curTime-1000) || newTimestamp > (curTime+1000)) { bBrokenTimestamp = true; Log(TEXT("MMDeviceAudioSource::GetNextBuffer: Got bad audio timestamp offset %lld from device: '%s', timestamps for this device will be calculated. curTime: %llu, newTimestamp: %llu"), (LONGLONG)(newTimestamp - curTime), GetDeviceName().Array(), curTime, newTimestamp); lastUsedTimestamp = newTimestamp = curTime; } else lastUsedTimestamp = newTimestamp; bFirstFrameReceived = true; } if(tempBuffer.Num() < numAudioFrames*2) tempBuffer.SetSize(numAudioFrames*2); outputBuffer = tempBuffer.Array(); float *tempOut = outputBuffer; //------------------------------------------------------------ // channel upmix/downmix if(inputChannels == 1) { UINT numFloats = numAudioFrames; float *inputTemp = (float*)captureBuffer; float *outputTemp = outputBuffer; if(App->SSE2Available() && (UPARAM(inputTemp) & 0xF) == 0 && (UPARAM(outputTemp) & 0xF) == 0) { UINT alignedFloats = numFloats & 0xFFFFFFFC; for(UINT i=0; i<alignedFloats; i += 4) { __m128 inVal = _mm_load_ps(inputTemp+i); __m128 outVal1 = _mm_unpacklo_ps(inVal, inVal); __m128 outVal2 = _mm_unpackhi_ps(inVal, inVal); _mm_store_ps(outputTemp+(i*2), outVal1); _mm_store_ps(outputTemp+(i*2)+4, outVal2); } numFloats -= alignedFloats; inputTemp += alignedFloats; outputTemp += alignedFloats*2; } while(numFloats--) { float inputVal = *inputTemp; *(outputTemp++) = inputVal; *(outputTemp++) = inputVal; inputTemp++; } } else if(inputChannels == 2) //straight up copy { if(App->SSE2Available()) SSECopy(outputBuffer, captureBuffer, numAudioFrames*2*sizeof(float)); else mcpy(outputBuffer, captureBuffer, numAudioFrames*2*sizeof(float)); } else { //todo: downmix optimization, also support for other speaker configurations than ones I can merely "think" of. ugh. float *inputTemp = (float*)captureBuffer; float *outputTemp = outputBuffer; if(inputChannelMask == KSAUDIO_SPEAKER_QUAD) { UINT numFloats = numAudioFrames*4; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float rear = (inputTemp[2]+inputTemp[3])*surroundMix; *(outputTemp++) = left - rear; *(outputTemp++) = right + rear; inputTemp += 4; } } else if(inputChannelMask == KSAUDIO_SPEAKER_2POINT1) { UINT numFloats = numAudioFrames*3; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float lfe = inputTemp[2]*lowFreqMix; *(outputTemp++) = left + lfe; *(outputTemp++) = right + lfe; inputTemp += 3; } } else if(inputChannelMask == KSAUDIO_SPEAKER_4POINT1) { UINT numFloats = numAudioFrames*5; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float lfe = inputTemp[2]*lowFreqMix; float rear = (inputTemp[3]+inputTemp[4])*surroundMix; *(outputTemp++) = left + lfe - rear; *(outputTemp++) = right + lfe + rear; inputTemp += 5; } } else if(inputChannelMask == KSAUDIO_SPEAKER_SURROUND) { UINT numFloats = numAudioFrames*4; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float center = inputTemp[2]*centerMix; float rear = inputTemp[3]*(surroundMix*dbMinus3); *(outputTemp++) = left + center - rear; *(outputTemp++) = right + center + rear; inputTemp += 4; } } //don't think this will work for both else if(inputChannelMask == KSAUDIO_SPEAKER_5POINT1) { UINT numFloats = numAudioFrames*6; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float center = inputTemp[2]*centerMix; float lowFreq = inputTemp[3]*lowFreqMix; float rear = (inputTemp[4]+inputTemp[5])*surroundMix; *(outputTemp++) = left + center + lowFreq - rear; *(outputTemp++) = right + center + lowFreq + rear; inputTemp += 6; } } //todo ------------------ //not sure if my 5.1/7.1 downmixes are correct else if(inputChannelMask == KSAUDIO_SPEAKER_5POINT1_SURROUND) { UINT numFloats = numAudioFrames*6; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float center = inputTemp[2]*centerMix; float lowFreq = inputTemp[3]*lowFreqMix; float sideLeft = inputTemp[4]*dbMinus3; float sideRight = inputTemp[5]*dbMinus3; *(outputTemp++) = left + center + sideLeft + lowFreq; *(outputTemp++) = right + center + sideRight + lowFreq; inputTemp += 6; } } else if(inputChannelMask == KSAUDIO_SPEAKER_7POINT1) { UINT numFloats = numAudioFrames*8; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float center = inputTemp[2]*(centerMix*dbMinus3); float lowFreq = inputTemp[3]*lowFreqMix; float rear = (inputTemp[4]+inputTemp[5])*surroundMix; float centerLeft = inputTemp[6]*dbMinus6; float centerRight = inputTemp[7]*dbMinus6; *(outputTemp++) = left + centerLeft + center + lowFreq - rear; *(outputTemp++) = right + centerRight + center + lowFreq + rear; inputTemp += 8; } } else if(inputChannelMask == KSAUDIO_SPEAKER_7POINT1_SURROUND) { UINT numFloats = numAudioFrames*8; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float center = inputTemp[2]*centerMix; float lowFreq = inputTemp[3]*lowFreqMix; float rear = (inputTemp[4]+inputTemp[5])*(surroundMix*dbMinus3); float sideLeft = inputTemp[6]*dbMinus6; float sideRight = inputTemp[7]*dbMinus6; *(outputTemp++) = left + sideLeft + center + lowFreq - rear; *(outputTemp++) = right + sideLeft + center + lowFreq + rear; inputTemp += 8; } } } mmCapture->ReleaseBuffer(numAudioFrames); //------------------------------------------------------------ // resample if(bResample) { UINT frameAdjust = UINT((double(numAudioFrames) * resampleRatio) + 1.0); UINT newFrameSize = frameAdjust*2; if(tempResampleBuffer.Num() < newFrameSize) tempResampleBuffer.SetSize(newFrameSize); SRC_DATA data; data.src_ratio = resampleRatio; data.data_in = tempBuffer.Array(); data.input_frames = numAudioFrames; data.data_out = tempResampleBuffer.Array(); data.output_frames = frameAdjust; data.end_of_input = 0; int err = src_process(resampler, &data); if(err) { RUNONCE AppWarning(TEXT("Was unable to resample audio")); return NoAudioAvailable; } if(data.input_frames_used != numAudioFrames) { RUNONCE AppWarning(TEXT("Failed to downsample buffer completely, which shouldn't actually happen because it should be using 10ms of samples")); return NoAudioAvailable; } numAudioFrames = data.output_frames_gen; } //----------------------------------------------------------------------------- // sort all audio frames into 10 millisecond increments (done because not all devices output in 10ms increments) // NOTE: 0.457+ - instead of using the timestamps from windows, just compare and make sure it stays within a 100ms of their timestamps float *newBuffer = (bResample) ? tempResampleBuffer.Array() : tempBuffer.Array(); if(storageBuffer.Num() == 0 && numAudioFrames == 441) { lastUsedTimestamp += 10; if(!bBrokenTimestamp) { QWORD difVal = GetQWDif(newTimestamp, lastUsedTimestamp); if(difVal > 70) lastUsedTimestamp = newTimestamp; } if(lastUsedTimestamp > lastSentTimestamp) { QWORD adjustVal = (lastUsedTimestamp-lastSentTimestamp); if(adjustVal < 10) lastUsedTimestamp += 10-adjustVal; AudioSegment &newSegment = *audioSegments.CreateNew(); newSegment.audioData.CopyArray(newBuffer, numAudioFrames*2); newSegment.timestamp = lastUsedTimestamp; MultiplyAudioBuffer(newSegment.audioData.Array(), numAudioFrames*2, curVolume); lastSentTimestamp = lastUsedTimestamp; } } else { UINT storedFrames = storageBuffer.Num(); storageBuffer.AppendArray(newBuffer, numAudioFrames*2); if(storageBuffer.Num() >= (441*2)) { lastUsedTimestamp += 10; if(!bBrokenTimestamp) { QWORD difVal = GetQWDif(newTimestamp, lastUsedTimestamp); if(difVal > 70) lastUsedTimestamp = newTimestamp - (QWORD(storedFrames)/2*1000/44100); } //------------------------ // add new data if(lastUsedTimestamp > lastSentTimestamp) { QWORD adjustVal = (lastUsedTimestamp-lastSentTimestamp); if(adjustVal < 10) lastUsedTimestamp += 10-adjustVal; AudioSegment &newSegment = *audioSegments.CreateNew(); newSegment.audioData.CopyArray(storageBuffer.Array(), (441*2)); newSegment.timestamp = lastUsedTimestamp; MultiplyAudioBuffer(newSegment.audioData.Array(), 441*2, curVolume); storageBuffer.RemoveRange(0, (441*2)); } //------------------------ // if still data pending (can happen) while(storageBuffer.Num() >= (441*2)) { lastUsedTimestamp += 10; if(lastUsedTimestamp > lastSentTimestamp) { QWORD adjustVal = (lastUsedTimestamp-lastSentTimestamp); if(adjustVal < 10) lastUsedTimestamp += 10-adjustVal; AudioSegment &newSegment = *audioSegments.CreateNew(); newSegment.audioData.CopyArray(storageBuffer.Array(), (441*2)); storageBuffer.RemoveRange(0, (441*2)); MultiplyAudioBuffer(newSegment.audioData.Array(), 441*2, curVolume); newSegment.timestamp = lastUsedTimestamp; lastSentTimestamp = lastUsedTimestamp; } } } } //----------------------------------------------------------------------------- return ContinueAudioRequest; } return NoAudioAvailable; }
void Triangulator::ConnectChibiLoops(ChibiLoopNode &loopNode, List<LoopVerts> &LoopList, List<LoopVerts> &NewLoopList) { DWORD i, j, k, l; List<PolyLine> CurLoop; CurLoop.CopyList(LoopList[loopNode.loop]); if(!ChibiLoopFacingUp(CurLoop)) ReverseLoop(CurLoop); List<DWORD> AlreadyUsed; for(i=0; i<loopNode.Children.Num(); i++) { ChibiLoopNode &childNode = loopNode.Children[i]; List<PolyLine> &childLoop = LoopList[childNode.loop]; if(ChibiLoopFacingUp(childLoop)) ReverseLoop(childLoop); } List<DWORD> CurChildren; CurChildren.SetSize(loopNode.Children.Num()); for(i=0; i<loopNode.Children.Num(); i++) CurChildren[i] = i; i = 0; while(CurChildren.Num()) { ChibiLoopNode &childNode = loopNode.Children[CurChildren[i]]; List<PolyLine> &childLoop = LoopList[childNode.loop]; //--------------------------------------------- // find closest points between loops DWORD curBestChoiceLoop1 = INVALID; DWORD curBestChoiceLoop2 = INVALID; float closestPos = 0.0f; for(j=0; j<CurLoop.Num(); j++) { if(AlreadyUsed.FindValueIndex(CurLoop[j].v1) != INVALID) continue; Vect2 &v1 = Verts[CurLoop[j].v1]; for(k=0; k<childLoop.Num(); k++) { if(AlreadyUsed.FindValueIndex(childLoop[k].v1) != INVALID) continue; Vect2 &v2 = Verts[childLoop[k].v1]; BOOL bBadChoice = LineIntersectsShape(v1, v2, CurLoop); if(bBadChoice) continue; for(l=0; l<CurChildren.Num(); l++) { if(LineIntersectsShape(v1, v2, LoopList[loopNode.Children[CurChildren[l]].loop])) { bBadChoice = TRUE; break; } } if(bBadChoice) continue; float dist = v2.Dist(v1); if((curBestChoiceLoop1 != INVALID) && (dist > closestPos)) continue; closestPos = dist; curBestChoiceLoop1 = j; curBestChoiceLoop2 = k; } } if(curBestChoiceLoop1 == INVALID) { i = (i == CurChildren.Num()-1) ? 0 : (i+1); if(i == 0) { AppWarning(TEXT("...almost infinitely looped there. Fortunately I have measures against such devious things.")); break; } continue; } AlreadyUsed << CurLoop[curBestChoiceLoop1].v1; AlreadyUsed << childLoop[curBestChoiceLoop2].v1; //--------------------------------------------- // connect loops if(CurLoop[0].lineData && !childLoop[0].lineData) { for(j=0; j<childLoop.Num(); j++) childLoop[j].lineData = CurLoop[0].lineData; } SetNewLoopStartPosition(CurLoop, curBestChoiceLoop1); SetNewLoopStartPosition(childLoop, curBestChoiceLoop2); if(!CurLoop[0].lineData && childLoop[0].lineData) { for(j=0; j<CurLoop.Num(); j++) CurLoop[j].lineData = childLoop[0].lineData; } PolyLine &lastLine = CurLoop.Last(); PolyLine *pLine = CurLoop.CreateNew(); pLine->v1 = lastLine.v2; pLine->v2 = childLoop[0].v1; CurLoop.AppendList(childLoop); pLine = CurLoop.CreateNew(); pLine->v1 = childLoop[0].v1; pLine->v2 = CurLoop[0].v1; /*for(j=0; j<CurLoop.Num(); j++) { String chong; chong << long(j) << TEXT(": x: ") << FormattedString(TEXT("%0.4f"), Verts[CurLoop[j].v1].x) << TEXT("\ty: ") << FormattedString(TEXT("%0.4f"), Verts[CurLoop[j].v1].y) << TEXT("\r\n"); OutputDebugString(chong); } OutputDebugString(TEXT("====================================\r\n"));*/ CurChildren.Remove(i); i = 0; } NewLoopList.CreateNew()->CopyList(CurLoop); CurLoop.Clear(); for(i=0; i<loopNode.Children.Num(); i++) { ChibiLoopNode &childNode = loopNode.Children[i]; for(j=0; j<childNode.Children.Num(); j++) ConnectChibiLoops(childNode.Children[j], LoopList, NewLoopList); } }
void Triangulator::CreateChibiLoopTree(List<ChibiLoopNode> &LoopNodeList, List<DWORD> &LoopRefs, List<LoopVerts> &LoopList) { int i, j; // find top loops /*for(i=0; i<LoopRefs.Num(); i++) { LoopVerts &loop1 = LoopList[LoopRefs[i]]; BOOL bTopLoop = TRUE; //Vect2 &testVert = Verts[LoopList[LoopRefs[i]][0].v1]; List<DWORD> ChildRefs; for(j=0; j<LoopRefs.Num(); j++) { if(j == i) continue; LoopVerts &loop2 = LoopList[LoopRefs[j]]; DWORD val = LoopInsideLoop(loop2, loop1); if(val == 2) { if(!ChibiLoopFacingUp(loop2)) val = 1; } if(val == 1) { bTopLoop = FALSE; break; } else if(LoopInsideLoop(loop1, loop2)) ChildRefs << LoopRefs[j]; } if(bTopLoop) { ChibiLoopNode &chibiLoopNode = *LoopNodeList.CreateNew(); chibiLoopNode.loop = LoopRefs[i]; CreateChibiLoopTree(chibiLoopNode.Children, ChildRefs, LoopList); } }*/ for(i=0; LoopRefs.Num(); i++) //works like a while(LoopRefs.Num()) { LoopVerts &loop1 = LoopList[LoopRefs[i]]; BOOL bTopLoop = TRUE; List<DWORD> ChildRefs; ChildRefs.Clear(); for(j=0; j<LoopRefs.Num(); j++) { if(j == i) continue; LoopVerts &loop2 = LoopList[LoopRefs[j]]; DWORD val = LoopInsideLoop(loop2, loop1); if(val == 2) { if(!ChibiLoopFacingUp(loop2)) val = 1; } if(val == 1) { bTopLoop = FALSE; break; } else { DWORD val2 = LoopInsideLoop(loop1, loop2); if((val2 == 1) || ((val2 & val) == 2)) ChildRefs << LoopRefs[j]; } } if(bTopLoop) { ChibiLoopNode &chibiLoopNode = *LoopNodeList.CreateNew(); chibiLoopNode.loop = LoopRefs[i]; LoopRefs.Remove(i); if(ChildRefs.Num()) { for(j=0; j<ChildRefs.Num(); j++) LoopRefs.RemoveItem(ChildRefs[j]); CreateChibiLoopTree(chibiLoopNode.Children, ChildRefs, LoopList); } i=-1; //because it'll do the i++ } } }
void Triangulator::Triangulate() { assert(Verts.Num()); if(!Verts.Num()) return; DWORD i; //if no lines available, build lines list from vert list if(!Lines.Num()) { Lines.SetSize(Verts.Num()); for(i=0; i<Verts.Num(); i++) { PolyLine &line = Lines[i]; int ip1 = (i == Verts.Num()-1) ? 0 : (i+1); line.v1 = i; line.v2 = ip1; } } List<ChibiLoopNode> ChibiLoopTree; List<LoopVerts> LoopList; DWORD curLoop=0; //----------------------------------------------------------- // find loops! if(blablabla == 1) blablabla = 0; List<DWORD> LineIDs; LineIDs.SetSize(Lines.Num()); for(i=0; i<LineIDs.Num(); i++) LineIDs[i] = i; while(LineIDs.Num()) { LoopVerts &CurLoop = *LoopList.CreateNew(); DWORD lastLine = 0; CurLoop.Clear(); while(TRUE) { PolyLine &line1 = Lines[LineIDs[lastLine]]; LineIDs.Remove(lastLine); CurLoop << line1; BOOL bNoResults = TRUE; if(line1.v2 == CurLoop[0].v1) break; for(i=0; i<LineIDs.Num(); i++) { PolyLine &line2 = Lines[LineIDs[i]]; if((line2.v1 == line1.v2) && (line2.v2 != line1.v1) && (!line2.lineData || (line1.lineData == line2.lineData))) { lastLine = i; line2.lineData = line1.lineData; bNoResults = FALSE; break; } } if(bNoResults) { AppWarning(TEXT("Invalid Loop data sent to triangulation.")); LineIDs.Clear(); break; } } } //----------------------------------------------------------- // sort loops! List<DWORD> LoopRefs; LoopRefs.SetSize(LoopList.Num()); for(i=0; i<LoopList.Num(); i++) LoopRefs[i] = i; CreateChibiLoopTree(ChibiLoopTree, LoopRefs, LoopList); LoopRefs.Clear(); //----------------------------------------------------------- // connect loops //SpreadLoopData(ChibiLoopTree, LoopList, 0); List<LoopVerts> NewLoopList; for(i=0; i<ChibiLoopTree.Num(); i++) ConnectChibiLoops(ChibiLoopTree[i], LoopList, NewLoopList); DestroyChibiLoopTree(ChibiLoopTree); for(int i=0; i<LoopList.Num(); i++) LoopList[i].Clear(); LoopList.Clear(); LoopList.SetSize(NewLoopList.Num()); for(i=0; i<NewLoopList.Num(); i++) { LoopList[i].CopyList(NewLoopList[i]); NewLoopList[i].Clear(); } NewLoopList.Clear(); for(i=0; i<LoopList.Num(); i++) { LoopVerts &loop = LoopList[i]; PolyLine &firstLine = loop[0]; for(int j=1; j<loop.Num(); j++) { PolyLine &line = loop[j]; line.lineData = firstLine.lineData; } } //----------------------------------------------------------- // triangulate! for(i=0; i<LoopList.Num(); i++) TriangulateShape(LoopList[i]); //----------------------------------------------------------- // clean up! Lines.Clear(); for(i=0; i<LoopList.Num(); i++) { Lines.AppendList(LoopList[i]); LoopList[i].Clear(); } LoopList.Clear(); }