zoukankan      html  css  js  c++  java
  • 协同旋转不变网格形变

      1.设置网格顶点局部标架

      定义顶点 Vi 的局部标架 Fi = (ei1, ei2, ni),如图

      

      三维空间中的任意向量 A 可用局部标架表示为 A = λ1e1 + λ2e2 + λ3n;

      2.求取矩阵 T

      两两局部标架之间有旋转矩阵 T(因为是局部标架而非局部坐标系,所以没有平移,不适用合同变换),如下

      

      每个顶点的标架矩阵 F 都是3 x 3的矩阵,设Fi = (ei1, ei2, ni)和 Fj = (ej1, ej2, nj)为顶点Vi 和 Vj的标架矩阵,Vj 为Vi 的一环邻域顶点,如图

      

      则对于旋转矩阵 Tij,有 Fj - TijFi = 0; 调整一下为 Tij 等于 Fj 乘以 Fi的逆。当世界坐标系发生变换时,标架 Fi 和 Fj 同样发生变化,但是矩阵 Tij 是不变的。

      此为网格的内在属性。

      3.求取更新后的局部标架

      和之前那篇,求取拉普拉斯矩阵差不多。只是网格顶点变化为 3 x 3 的矩阵而已,如下

      

      关于矩阵 G ,其中每一行的分量是为顶点 Vi 对于其一阶邻域顶点 Vj 的旋转矩阵 Tji ,每一列的分量是为 一阶邻域顶点 Vj 到 顶点 Vi 的旋转矩阵 Tij

      关于矩阵 H ,和拉普拉斯矩阵中的约束调参顶点一样,但是要修改为 3 x 3的单位矩阵 i。 当 R矩阵中的约束顶点标架发生变化时,便可以求取新的顶点标架 F ' 。

      4. 求取旋转不变网格之后的拉普拉斯网格形变

      和之前求取拉普拉斯坐标δ一样,只是需要将全局拉普拉斯坐标转换为局部标架下的局部拉普拉斯坐标δi

      

      ,

      当步骤3求取到每个顶点的新标架F之后,求取新的局部拉普拉斯坐标,进而求得拉普拉斯坐标。之后就可以和求取拉普拉斯算子矩阵方程一样,解稀疏线性方程组。

      

      

      

      

      构建局部标架以及原始旋转矩阵T和局部拉普拉斯坐标

     1 void mainNode::InitAxesAndMatrixSelf()
     2 {
     3     //先构建局部坐标系
     4     for (int i = 0; i < m_vecAllVertex.size(); i++)
     5     {
     6         pVERTEX pVertex = m_vecAllVertex.at(i);
     7         std::vector<pTRIANGLE> vectorTri = pVertex->vecNeighborTri;
     8         osg::Vec3 vertexNormal, E1, E2;
     9         vertexNormal = E1 = E2 = osg::Vec3(0.0, 0.0, 0.0);
    10         int sizeOfTri = vectorTri.size();
    11         for (int j = 0; j < sizeOfTri; j++)
    12         {
    13             pTRIANGLE pTri = vectorTri.at(j);
    14             pVERTEX pA = pTri->pA;
    15             pVERTEX pB = pTri->pB;
    16             pVERTEX pC = pTri->pC;
    17             osg::Vec3 BA = pA->pos - pB->pos;
    18             osg::Vec3 BC = pC->pos - pB->pos;
    19             osg::Vec3 normal = BA ^ BC;
    20             normal.normalize();
    21             vertexNormal += normal;
    22         }
    23         vertexNormal /= sizeOfTri;
    24         E1 = osg::Vec3( - vertexNormal.y(), vertexNormal.x(), 0.0);
    25         E2 = vertexNormal ^ E1;
    26         vertexNormal.normalize();
    27         E1.normalize();
    28         E2.normalize();
    29         pVertex->E1 = E1;
    30         pVertex->E2 = E2;
    31         pVertex->N = vertexNormal;
    32         pVertex->axesSelf.resize(3, 3);
    33         std::vector<Eigen::Triplet<float>> vectorTriplet;
    34         vectorTriplet.push_back(Eigen::Triplet<float>(0, 0, E1.x()));
    35         vectorTriplet.push_back(Eigen::Triplet<float>(1, 0, E1.y()));
    36         vectorTriplet.push_back(Eigen::Triplet<float>(2, 0, E1.z()));
    37 
    38         vectorTriplet.push_back(Eigen::Triplet<float>(0, 1, E2.x()));
    39         vectorTriplet.push_back(Eigen::Triplet<float>(1, 1, E2.y()));
    40         vectorTriplet.push_back(Eigen::Triplet<float>(2, 1, E2.z()));
    41 
    42         vectorTriplet.push_back(Eigen::Triplet<float>(0, 2, vertexNormal.x()));
    43         vectorTriplet.push_back(Eigen::Triplet<float>(1, 2, vertexNormal.y()));
    44         vectorTriplet.push_back(Eigen::Triplet<float>(2, 2, vertexNormal.z()));
    45 
    46         Eigen::SparseMatrix<float> matrixTemp(3, 3);
    47         matrixTemp.setFromTriplets(vectorTriplet.begin(), vectorTriplet.end());
    48         pVertex->axesSelf = matrixTemp;
    49         //构建相对局部坐标系的拉普拉斯坐标长度分量
    50         pVertex->relativeX = pVertex->lplsPos * E1;
    51         pVertex->relativeY = pVertex->lplsPos * E2;
    52         pVertex->relativeZ = pVertex->lplsPos * vertexNormal;
    53     }
    54     //构建周围一阶顶点到此顶点的转换矩阵
    55     for (int i = 0; i < m_vecAllVertex.size(); i++)
    56     {
    57         pVERTEX pVertex = m_vecAllVertex.at(i);
    58         Eigen::Matrix3f axesSelf = pVertex->axesSelf;
    59         //Eigen::Matrix3f matrixSelf = axesSelf;
    60         std::vector<pVERTEX> vecNeighborVertex = pVertex->vecNeighborVertex;
    61         for (int j = 0; j < vecNeighborVertex.size(); j++)
    62         {
    63             pVERTEX pNeighbor = vecNeighborVertex.at(j);
    64             Eigen::Matrix3f axesOther = pNeighbor->axesSelf;
    65             //std::cout << axesOther << std::endl;
    66             Eigen::Matrix3f axesOtherInverse = axesOther.inverse();
    67             //std::cout << axesOtherInverse << std::endl;
    68             // Ax = b; T * axesOther = axesSelf;
    69             Eigen::Matrix3f  matrixT = axesSelf * axesOtherInverse;
    70             pVertex->mapMatrixOtherToSelf.insert(std::pair<int, Eigen::Matrix3f>(pNeighbor->id, matrixT));
    71             //test
    72             Eigen::Matrix3f matrixTemp = matrixT * axesOther;
    73             //std::cout << matrixTemp << std::endl;
    74             //std::cout << axesSelf << std::endl;
    75         }
    76     }
    77 }
    View Code

      构建旋转矩阵方程组

     1 void mainNode::InitNewAxesMatrix()
     2 {
     3     int sizeOfVertex = m_vecAllVertex.size();
     4     std::vector<Eigen::Triplet<float> > vectorTriplet;
     5     for (int i = 0; i < sizeOfVertex; i++)
     6     {
     7         pVERTEX pVertex = m_vecAllVertex.at(i);
     8         int vertexID = pVertex->id;
     9         float sizeOfNeighborVertex = pVertex->vecNeighborVertex.size();
    10         
    11         vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3, vertexID * 3, - sizeOfNeighborVertex));
    12         vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3 + 1, vertexID * 3 + 1, - sizeOfNeighborVertex));
    13         vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3 + 2, vertexID * 3 + 2, - sizeOfNeighborVertex));
    14 
    15         std::map<int, Eigen::Matrix3f>::iterator itorOfT;
    16         for(itorOfT = pVertex->mapMatrixOtherToSelf.begin(); itorOfT != pVertex->mapMatrixOtherToSelf.end(); itorOfT++)
    17         {
    18             int vertexNeighborID = itorOfT->first;
    19             Eigen::Matrix3f T = itorOfT->second;
    20             vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3, vertexNeighborID * 3, T(0, 0)));
    21             vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3, vertexNeighborID * 3 + 1, T(0, 1)));
    22             vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3, vertexNeighborID * 3 + 2, T(0, 2)));
    23 
    24             vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3 + 1, vertexNeighborID * 3, T(1, 0)));
    25             vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3 + 1, vertexNeighborID * 3 + 1, T(1, 1)));
    26             vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3 + 1, vertexNeighborID * 3 + 2, T(1, 2)));
    27 
    28             vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3 + 2, vertexNeighborID * 3, T(2, 0)));
    29             vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3 + 2, vertexNeighborID * 3 + 1, T(2, 1)));
    30             vectorTriplet.push_back(Eigen::Triplet<float>(vertexID * 3 + 2, vertexNeighborID * 3 + 2, T(2, 2)));
    31         }
    32     }
    33     //添加约束顶点;头一圈和尾一圈,可自己调节
    34     for (int i = 0; i < m_iAroundNumber * 2; i++)
    35     {
    36         pVERTEX pVertex;
    37         int vertexID;
    38         float value = 1.0;
    39         if (i < m_iAroundNumber)
    40         {
    41             pVertex = m_vecAllVertex.at(i);
    42             vertexID = pVertex->id;
    43         }
    44         else
    45         {
    46             pVertex = m_vecAllVertex.at(i + m_iAroundNumber * (m_iHeightNumber - 2));
    47             vertexID = pVertex->id;
    48         }
    49         vectorTriplet.push_back(Eigen::Triplet<float>(sizeOfVertex * 3 + i * 3, vertexID * 3, value));
    50         vectorTriplet.push_back(Eigen::Triplet<float>(sizeOfVertex * 3 + i * 3 + 1, vertexID * 3 + 1, value));
    51         vectorTriplet.push_back(Eigen::Triplet<float>(sizeOfVertex * 3 + i * 3 + 2, vertexID * 3 + 2, value));
    52     }
    53 
    54     m_NewAxesMatrix.resize(sizeOfVertex * 3 + (m_iAroundNumber * 2 * 3), sizeOfVertex * 3);
    55     m_NewAxesMatrix.setFromTriplets(vectorTriplet.begin(), vectorTriplet.end());
    56 }
    View Code

      求取新的局部标架并获得新的拉普拉斯坐标

     1 void mainNode::CalculateNewAxes()
     2 {
     3     Eigen::SparseMatrix<float> matrixNewAxesTrans = m_NewAxesMatrix.transpose();
     4     Eigen::SparseMatrix<float> matrixRes = matrixNewAxesTrans * m_NewAxesMatrix;
     5     Eigen::SimplicialCholesky<Eigen::SparseMatrix<float> > matrixCholesky(matrixRes);
     6 
     7     std::vector<Eigen::Triplet<float> > vectorTriplet;
     8     int sizeOfVertex = m_vecAllVertex.size();
     9     //方程式等号右边
    10     Eigen::VectorXf vectorX, vectorY, vectorZ;
    11     vectorX.resize(sizeOfVertex * 3 + m_iAroundNumber * 2 * 3);
    12     vectorX.setZero();
    13     vectorY.resize(sizeOfVertex * 3 + m_iAroundNumber * 2 * 3);
    14     vectorY.setZero();
    15     vectorZ.resize(sizeOfVertex * 3 + m_iAroundNumber * 2 * 3);
    16     vectorZ.setZero();
    17     for (int i = 0; i < m_iAroundNumber * 2; i++)
    18     {
    19         pVERTEX pVertex;
    20         int vertexID;
    21         Eigen::Matrix3f matrixSelf;
    22         float value = 1.0;
    23         if (i < m_iAroundNumber)
    24         {
    25             pVertex = m_vecAllVertex.at(i);
    26         }
    27         else
    28         {
    29             pVertex = m_vecAllVertex.at(i + m_iAroundNumber * (m_iHeightNumber - 2));    
    30         }
    31         vertexID = pVertex->id;
    32         matrixSelf = pVertex->axesSelf;
    33 
    34         vectorX[sizeOfVertex * 3 + i * 3] = matrixSelf(0, 0);
    35         vectorX[sizeOfVertex * 3 + i * 3 + 1] = matrixSelf(1, 0);
    36         vectorX[sizeOfVertex * 3 + i * 3 + 2] = matrixSelf(2, 0);
    37 
    38         vectorY[sizeOfVertex * 3 + i * 3] = matrixSelf(0, 1);
    39         vectorY[sizeOfVertex * 3 + i * 3 + 1] = matrixSelf(1, 1);
    40         vectorY[sizeOfVertex * 3 + i * 3 + 2] = matrixSelf(2, 1);
    41 
    42         vectorZ[sizeOfVertex * 3 + i * 3] = matrixSelf(0, 2);
    43         vectorZ[sizeOfVertex * 3 + i * 3 + 1] = matrixSelf(1, 2);
    44         vectorZ[sizeOfVertex * 3 + i * 3 + 2] = matrixSelf(2, 2);
    45     }
    46     
    47     vectorX = matrixNewAxesTrans * vectorX;
    48     vectorY = matrixNewAxesTrans * vectorY;
    49     vectorZ = matrixNewAxesTrans * vectorZ;
    50 
    51     Eigen::VectorXf vectorResX, vectorResY, vectorResZ;
    52     vectorResX = matrixCholesky.solve(vectorX);
    53     vectorResY = matrixCholesky.solve(vectorY);
    54     vectorResZ = matrixCholesky.solve(vectorZ);
    55     //得到新的标架
    56     for (int i = 0; i < m_vecAllVertex.size(); i++)
    57     {
    58         Eigen::Matrix3f matrixSelf;
    59         pVERTEX pVertex = m_vecAllVertex.at(i);
    60         float x1, x2, x3, y1, y2, y3, z1, z2, z3;
    61         x1 = vectorResX[i * 3];
    62         x2 = vectorResX[i * 3 + 1];
    63         x3 = vectorResX[i * 3 + 2];
    64 
    65         y1 = vectorResY[i * 3];
    66         y2 = vectorResY[i * 3 + 1];
    67         y3 = vectorResY[i * 3 + 2];
    68 
    69         z1 = vectorResZ[i * 3];
    70         z2 = vectorResZ[i * 3 + 1];
    71         z3 = vectorResZ[i * 3 + 2];
    72 
    73         pVertex->E1 = osg::Vec3(x1, x2, x3);
    74         pVertex->E2 = osg::Vec3(y1, y2, y3);
    75         pVertex->N = osg::Vec3(z1, z2, z3);
    76 
    77         //新的拉普拉斯坐标
    78         pVertex->lplsPos = (pVertex->E1 * pVertex->relativeX) + (pVertex->E2 * pVertex->relativeY) + (pVertex->N * pVertex->relativeZ);
    79     }
    80 }
    View Code

      构建拉普拉斯矩阵

      1 void mainNode::InitLpls()
      2 {
      3     int vertexNumber = m_vecAllVertex.size();
      4     //计算顶点一阶临接顶点的权值,方式一
      5     for (int i = 0; i < vertexNumber; i++)
      6     {
      7         pVERTEX pVertex = m_vecAllVertex.at(i);
      8         int neighborNumber = pVertex->vecNeighborVertex.size();
      9         for (int j = 0; j < neighborNumber; j++)
     10         {
     11             pVERTEX pNeighbor = pVertex->vecNeighborVertex.at(j);
     12             float weight = float(1) / float(neighborNumber);
     13             pVertex->mapWeightForOther.insert(std::pair<int, float>(pNeighbor->id, weight) );
     14             pVertex->fTotalWeight = 1.0;
     15         }
     16     }
     17 
     18     /*
     19     //构建拉普拉斯矩阵权值,方式二
     20     for (int i = 0; i < vertexNumber; i++)
     21     {
     22         pVERTEX pVertex = m_vecAllVertex.at(i);
     23         float totalWeight = 0.0;
     24         for (int j = 0; j < pVertex->vecNeighborEdge.size(); j++)
     25         {
     26             pEDGE pEdge = pVertex->vecNeighborEdge.at(j);
     27             pVERTEX pA = pEdge->pA;
     28             pVERTEX pB = pEdge->pB;
     29 
     30             pVERTEX pTarget;
     31             if (pA->id == pVertex->id)
     32                 pTarget = pB;
     33             else
     34                 pTarget = pA;
     35 
     36             std::vector<pTRIANGLE> vecTri = pEdge->vecNeighborTri;
     37             pVERTEX pC = NULL;
     38             float weight = 0.0;
     39             for (int k = 0; k < vecTri.size(); k++)
     40             {
     41                 pTRIANGLE pTri = vecTri.at(k);
     42                 pVERTEX p1 = pTri->pA;
     43                 pVERTEX p2 = pTri->pB;
     44                 pVERTEX p3 = pTri->pC;
     45                 if ((pA->id == p1->id && pB->id == p2->id) || (pA->id == p2->id && pB->id == p1->id))
     46                 {
     47                     pC = p3;
     48                 }
     49                 else if ((pA->id == p1->id && pB->id == p3->id) || (pA->id == p3->id && pB->id == p1->id))
     50                 {
     51                     pC = p2;
     52                 }
     53                 else if ((pA->id == p2->id && pB->id == p3->id) || (pA->id == p3->id && pB->id == p2->id))
     54                 {
     55                     pC = p1;
     56                 }
     57                 //开始求取权值
     58                 float cotAngle = 0.0;
     59                 GetCotAngle(pA->pos, pB->pos, pC->pos, cotAngle);
     60                 weight += cotAngle;
     61             }
     62             weight /= 2.0;
     63             pVertex->mapWeightForOther.insert(std::pair<int, float>(pTarget->id, weight) );
     64             totalWeight += weight;
     65         }
     66         pVertex->fTotalWeight = totalWeight;
     67     }
     68     */
     69     //计算拉普拉斯坐标
     70     for (int i = 0; i < vertexNumber; i++)
     71     {
     72         pVERTEX pVertex = m_vecAllVertex.at(i);
     73         osg::Vec3 pos = pVertex->pos;
     74         pos = pos * pVertex->fTotalWeight;
     75         osg::Vec3 otherPos = osg::Vec3(0.0, 0.0, 0.0);
     76         for (int j = 0; j < pVertex->vecNeighborVertex.size(); j++)
     77         {
     78             pVERTEX pNeihbor = pVertex->vecNeighborVertex.at(j);
     79             std::map<int, float>::iterator itor = pVertex->mapWeightForOther.find(pNeihbor->id);
     80             float weight = itor->second;
     81             otherPos += pNeihbor->pos * weight;
     82         }
     83         pVertex->lplsPos = pos - otherPos;
     84     }
     85     
     86     int count = 0;
     87     std::vector<int> beginNumber(vertexNumber);
     88     for (int i = 0; i < vertexNumber; i++)
     89     {
     90         beginNumber[i] = count;
     91         count += m_vecAllVertex[i]->vecNeighborVertex.size() + 1;
     92     }
     93 
     94     std::vector<Eigen::Triplet<float> > vectorTriplet(count);
     95     for (int i = 0; i < vertexNumber; i++)
     96     {
     97         pVERTEX pVertex = m_vecAllVertex.at(i);
     98         //原始拉普拉斯矩阵
     99         vectorTriplet[beginNumber[i]] = Eigen::Triplet<float>(i, i, pVertex->fTotalWeight);
    100         int j = 0;
    101         std::map<int, float>::iterator itor;
    102         for(itor = pVertex->mapWeightForOther.begin(); itor != pVertex->mapWeightForOther.end(); itor++)
    103         {
    104             int neighborID = itor->first;
    105             float weight = itor->second;
    106             vectorTriplet[beginNumber[i] + j + 1] = Eigen::Triplet<float>(i, neighborID, -weight);
    107             j++;
    108         }
    109     }
    110 
    111     //头一圈和顶一圈
    112     for (int i = 0; i < m_iAroundNumber * 2; i++)
    113     {
    114         float weight = 1.0;
    115         pVERTEX pVertex;
    116         if (i < m_iAroundNumber)
    117         {
    118             pVertex = m_vecAllVertex.at(i);
    119         }
    120         else
    121         {
    122             pVertex = m_vecAllVertex.at(i + m_iAroundNumber * 14);
    123         }
    124 
    125         int row = i + vertexNumber;
    126         vectorTriplet.push_back(Eigen::Triplet<float>(row, pVertex->id, weight));
    127     }
    128     
    129     m_Matrix.resize(vertexNumber + m_iAroundNumber * 2, vertexNumber);
    130     //m_Matrix.resize(vertexNumber, vertexNumber);
    131     m_Matrix.setFromTriplets(vectorTriplet.begin(), vectorTriplet.end());
    132     //std::cout << m_Matrix << std::endl;
    133 }
    View Code

      根据新的拉普拉斯坐标求取新的顶点坐标

     1 void mainNode::CalculateNewMatrixLpls()
     2 {
     3     Eigen::SparseMatrix<float> Matrix = m_Matrix;
     4 
     5     Eigen::SparseMatrix<float> MatrixTranspose = Matrix.transpose();
     6     Eigen::SparseMatrix<float> MatrixLpls = MatrixTranspose * Matrix;
     7 
     8     Eigen::VectorXf targetX, targetY, targetZ;
     9     int vertexNumber = m_vecAllVertex.size();
    10 
    11     targetX.resize(vertexNumber + m_iAroundNumber * 2);
    12     targetY.resize(vertexNumber + m_iAroundNumber * 2);
    13     targetZ.resize(vertexNumber + m_iAroundNumber * 2);
    14 
    15     //方程式等号右边
    16     for (int i = 0; i < vertexNumber + m_iAroundNumber * 2; i++)
    17     {
    18         if (i < vertexNumber)
    19         {
    20             targetX[i] = m_vecAllVertex.at(i)->lplsPos[0];
    21             targetY[i] = m_vecAllVertex.at(i)->lplsPos[1];
    22             targetZ[i] = m_vecAllVertex.at(i)->lplsPos[2];
    23         }
    24         else if (i < vertexNumber + m_iAroundNumber)
    25         {
    26             //第一层的顶点坐标分量
    27             targetX[i] = m_vecAllVertex.at(i - vertexNumber)->pos.x();
    28             targetY[i] = m_vecAllVertex.at(i - vertexNumber)->pos.y();
    29             targetZ[i] = m_vecAllVertex.at(i - vertexNumber)->pos.z();
    30         }
    31         else
    32         {
    33             //最高层的顶点坐标分量
    34             targetX[i] = m_vecAllVertex.at((i - (vertexNumber + m_iAroundNumber) + (vertexNumber - m_iAroundNumber)))->pos.x();
    35             targetY[i] = m_vecAllVertex.at((i - (vertexNumber + m_iAroundNumber) + (vertexNumber - m_iAroundNumber)))->pos.y();
    36             targetZ[i] = m_vecAllVertex.at((i - (vertexNumber + m_iAroundNumber) + (vertexNumber - m_iAroundNumber)))->pos.z();
    37         }
    38     }
    39     
    40     Eigen::SimplicialCholesky<Eigen::SparseMatrix<float> > MatrixCholesky(MatrixLpls);
    41     Eigen::VectorXf resX, resY, resZ;
    42     resX = MatrixTranspose * targetX;
    43     resY = MatrixTranspose * targetY;
    44     resZ = MatrixTranspose * targetZ;
    45 
    46     Eigen::VectorXf X, Y, Z;
    47     X = MatrixCholesky.solve(resX);
    48     //std::cout << X << std::endl;
    49     Y = MatrixCholesky.solve(resY);
    50     //std::cout << Y << std::endl;
    51     Z = MatrixCholesky.solve(resZ);
    52     //std::cout << Z << std::endl;
    53     for (int i = 0; i < m_vecAllVertex.size(); i++)
    54     {
    55         pVERTEX pVertex = m_vecAllVertex.at(i);
    56         float x, y, z;
    57         x = X[i];
    58         y = Y[i];
    59         z = Z[i];
    60 
    61         pVertex->pos = osg::Vec3(X[i], Y[i], Z[i]);
    62     }
    63 }
    View Code

      参考文献:

      Linear Rotation-invariant Coordinates for Meshes

      Laplacian-Surface-Editing

      http://blog.csdn.net/hjimce/article/details/46415435

    转载于:https://www.cnblogs.com/TooManyWayToBe/p/8465470.html

  • 相关阅读:
    定时器
    WPF拖动总结
    将两个不同进程的窗口设置为父子关系
    Docker私有仓库管理
    Dockerfile创建zabbix监控体系
    Dockfile自动创建discuz论坛和可道云
    Docker的自动构建镜像
    Docker简介
    Mapreduce
    分布式文件系统与HDFS
  • 原文地址:https://www.cnblogs.com/twodog/p/12137467.html
Copyright © 2011-2022 走看看