| 749 | | double extentA = fabs(extent); |
|---|
| 750 | | if (extentA > PI2) |
|---|
| 751 | | { |
|---|
| 752 | | extent = PI2; // sign doesn't matter anymore |
|---|
| 753 | | extentA = PI2; |
|---|
| 754 | | } |
|---|
| 755 | | |
|---|
| 756 | | // we will approximate one full ellipse by 8 cubics, so here compute |
|---|
| 757 | | // what portion of a full ellipse we have so that we know how many |
|---|
| 758 | | // cubics will be needed |
|---|
| 759 | | int num_segs = (int)ceil(8.0 * extentA / PI2); |
|---|
| 760 | | double increment = extent / num_segs; |
|---|
| 761 | | |
|---|
| 762 | | double angle = startRad; |
|---|
| 763 | | double ellx = cos(angle); |
|---|
| 764 | | double elly = sin(angle); |
|---|
| 765 | | |
|---|
| 766 | | double Ke = CubicApproxParameter(increment*0.5); |
|---|
| 767 | | |
|---|
| 768 | | double c0x, c0y, c3x(0.0), c3y(0.0); |
|---|
| 769 | | for (int i=0; i<num_segs; ++i) |
|---|
| 770 | | { |
|---|
| 771 | | // move to start point |
|---|
| 772 | | if (i==0) |
|---|
| 773 | | { |
|---|
| 774 | | c0x = cx + a * ellx; |
|---|
| 775 | | c0y = cy + b * elly; |
|---|
| 776 | | LineTo(c0x, c0y); |
|---|
| 777 | | } |
|---|
| 778 | | else |
|---|
| 779 | | { |
|---|
| 780 | | c0x = c3x; |
|---|
| 781 | | c0y = c3y; |
|---|
| 782 | | } |
|---|
| 783 | | |
|---|
| 784 | | double c1x = cx + a*(ellx - Ke * elly); |
|---|
| 785 | | double c1y = cy + b*(elly + Ke * ellx); |
|---|
| 786 | | |
|---|
| 787 | | angle += increment; |
|---|
| 788 | | ellx = cos(angle); |
|---|
| 789 | | elly = sin(angle); |
|---|
| 790 | | |
|---|
| 791 | | double c2x = cx + a * (ellx + Ke * elly); |
|---|
| 792 | | double c2y = cy + b * (elly - Ke * ellx); |
|---|
| 793 | | c3x = cx + ellx * a; |
|---|
| 794 | | c3y = cy + elly * b; |
|---|
| 795 | | |
|---|
| 796 | | TessellateCubicTo(c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y); |
|---|
| | 737 | int nSegs = ARC_TESSELLATION_SEGMENTS; // default |
|---|
| | 738 | if (m_drawingScale != 0.0) |
|---|
| | 739 | { |
|---|
| | 740 | // Compute the required angular separation between points which gives |
|---|
| | 741 | // the desired tolerance. Just base it off the max radius and use the |
|---|
| | 742 | // formula for a circle with 0.25 pixel tolerance. Note that we don't |
|---|
| | 743 | // need to take into account any transformation in the case of 3D arcs |
|---|
| | 744 | // (see CircularArcTo3D). The case requiring the most segments is when |
|---|
| | 745 | // the plane of the 3D arc is normal to the Z axis. Since CircularArcTo3D |
|---|
| | 746 | // does the tessellation in this plane, this already corresponds to the |
|---|
| | 747 | // worst case. |
|---|
| | 748 | double maxRadius = rs_max(a, b); |
|---|
| | 749 | double dRadReq = sqrt(2.0 * m_drawingScale / maxRadius); |
|---|
| | 750 | |
|---|
| | 751 | // using the angular separation we can compute the minimum number |
|---|
| | 752 | // of segments |
|---|
| | 753 | nSegs = 1 + (int)(fabs(extent) / dRadReq); |
|---|
| | 754 | |
|---|
| | 755 | // the number of segments can get big for large arcs displayed at high |
|---|
| | 756 | // zoom levels, so limit the number of segments |
|---|
| | 757 | nSegs = rs_min(nSegs, 10 * ARC_TESSELLATION_SEGMENTS); |
|---|
| | 758 | } |
|---|
| | 759 | |
|---|
| | 760 | // get the angular separation corresponding to the number of segments |
|---|
| | 761 | double dRad = extent / nSegs; |
|---|
| | 762 | |
|---|
| | 763 | // add the segments |
|---|
| | 764 | EnsurePoints(nSegs); |
|---|
| | 765 | for (int i=1; i<=nSegs; ++i) |
|---|
| | 766 | { |
|---|
| | 767 | double ang = startRad + i*dRad; |
|---|
| | 768 | double tx = a * cos(ang); |
|---|
| | 769 | double ty = b * sin(ang); |
|---|
| | 770 | |
|---|
| | 771 | double x = cx + tx; |
|---|
| | 772 | double y = cy + ty; |
|---|
| | 773 | |
|---|
| | 774 | LineTo(x, y); |
|---|
| 801 | | } |
|---|
| 802 | | |
|---|
| 803 | | |
|---|
| 804 | | void LineBuffer::TessellateCubicTo(double px1, double py1, double px2, double py2, double px3, double py3, double px4, double py4) |
|---|
| 805 | | { |
|---|
| 806 | | double w = m_bounds.width(); |
|---|
| 807 | | double h = m_bounds.height(); |
|---|
| 808 | | double minSegLen = 0.0; |
|---|
| 809 | | double dt = INV_TESSELLATION_ITERATIONS; //= 1.0 / 100.0 Iterate 100 times through loop |
|---|
| 810 | | |
|---|
| 811 | | if (m_drawingScale != 0.0) |
|---|
| 812 | | { |
|---|
| 813 | | // If we have drawing scale available, use drawing scale to make sure |
|---|
| 814 | | // the minimum segment length is equal to # of screen units occupied |
|---|
| 815 | | // by one pixel. This ensures no "flat" segments are visible along |
|---|
| 816 | | // the curve. |
|---|
| 817 | | // minSegLen = m_drawingScale; |
|---|
| 818 | | |
|---|
| 819 | | // Setting the minimum segment length to the drawing scale creates so |
|---|
| 820 | | // many segments in dense drawings at high zoom levels that we run out |
|---|
| 821 | | // of memory. Use the larger of the drawing scale and one two hundredth |
|---|
| 822 | | // of the "size" of the arc we are tessellating. This compromise seems |
|---|
| 823 | | // to work in all "reasonable" cases. |
|---|
| 824 | | |
|---|
| 825 | | // Compute bounding box of the four control points of the curve |
|---|
| 826 | | double minx = min4(px1, px2, px3, px4); |
|---|
| 827 | | double miny = min4(py1, py2, py3, py4); |
|---|
| 828 | | double maxx = max4(px1, px2, px3, px4); |
|---|
| 829 | | double maxy = max4(py1, py2, py3, py4); |
|---|
| 830 | | |
|---|
| 831 | | double deltax = maxx - minx; |
|---|
| 832 | | double deltay = maxy - miny; |
|---|
| 833 | | |
|---|
| 834 | | double maxlength = rs_max(deltax, deltay); |
|---|
| 835 | | |
|---|
| 836 | | // 0.005 (1/200) is an arbitrary number, but it seems to work well |
|---|
| 837 | | minSegLen = rs_max(m_drawingScale, 0.005*maxlength); |
|---|
| 838 | | |
|---|
| 839 | | // This code will compute the # of pixels occupied by the curve segment. |
|---|
| 840 | | // It can then reduce the # of iterations in the forward differencing |
|---|
| 841 | | // loop to that # of pixels. This reduces the time taken to generate |
|---|
| 842 | | // each segment of the tessellation. |
|---|
| 843 | | |
|---|
| 844 | | // Figure out how many pixels are covered by that max length. |
|---|
| 845 | | double numPixels = maxlength / m_drawingScale; |
|---|
| 846 | | |
|---|
| 847 | | // Ensure that the # of loop iterations is at most 100 or # of pixels |
|---|
| 848 | | // covered (whichever is smaller). |
|---|
| 849 | | dt = rs_max(INV_TESSELLATION_ITERATIONS, (1.0 / numPixels)); |
|---|
| 850 | | } |
|---|
| 851 | | else |
|---|
| 852 | | { |
|---|
| 853 | | // We will base the max number of segments to use for approximation |
|---|
| 854 | | // on the bounds of the full line buffer contents. |
|---|
| 855 | | // TODO: as an improvement we could take the bounds of this particular |
|---|
| 856 | | // curve with respect to the full bounds of the line buffer data. |
|---|
| 857 | | double maxdim = rs_max(w, h); |
|---|
| 858 | | |
|---|
| 859 | | // minimum length of tessellation segment, set to 1/100 of the bounds |
|---|
| 860 | | minSegLen = maxdim * 0.01; |
|---|
| 861 | | } |
|---|
| 862 | | |
|---|
| 863 | | // double dt2 = dt*dt; |
|---|
| 864 | | double dt3 = dt * dt * dt; |
|---|
| 865 | | |
|---|
| 866 | | double pre1 = 3.0 * dt; |
|---|
| 867 | | double pre2 = pre1 * dt; |
|---|
| 868 | | double pre3 = pre2 + pre2; |
|---|
| 869 | | double pre4 = 6.0 * dt3; |
|---|
| 870 | | |
|---|
| 871 | | double temp1x = px1 - 2.0 * px2 + px3; |
|---|
| 872 | | double temp1y = py1 - 2.0 * py2 + py3; |
|---|
| 873 | | double temp2x = 3.0 * (px2 - px3) - px1 + px4; |
|---|
| 874 | | double temp2y = 3.0 * (py2 - py3) - py1 + py4; |
|---|
| 875 | | |
|---|
| 876 | | double fx = px1; |
|---|
| 877 | | double fy = py1; |
|---|
| 878 | | double dfx = (px2 - px1) * pre1 + temp1x * pre2 + temp2x * dt3; |
|---|
| 879 | | double dfy = (py2 - py1) * pre1 + temp1y * pre2 + temp2y * dt3; |
|---|
| 880 | | double ddfx = temp1x * pre3 + temp2x * pre4; |
|---|
| 881 | | double ddfy = temp1y * pre3 + temp2y * pre4; |
|---|
| 882 | | double dddfx = temp2x * pre4; |
|---|
| 883 | | double dddfy = temp2y * pre4; |
|---|
| 884 | | |
|---|
| 885 | | double error = 0.0; |
|---|
| 886 | | |
|---|
| 887 | | // forward differencing loop |
|---|
| 888 | | int tMax = (int)(1.0/dt - 0.5); |
|---|
| 889 | | for (int t=0; t<tMax; ++t) |
|---|
| 890 | | { |
|---|
| 891 | | fx += dfx; |
|---|
| 892 | | fy += dfy; |
|---|
| 893 | | dfx += ddfx; |
|---|
| 894 | | dfy += ddfy; |
|---|
| 895 | | ddfy += dddfy; |
|---|
| 896 | | ddfx += dddfx; |
|---|
| 897 | | |
|---|
| 898 | | // slow but accurate for flattening |
|---|
| 899 | | // error += sqrt(dfx*dfx + dfy*dfy); |
|---|
| 900 | | |
|---|
| 901 | | // faster but less accurate error estimate |
|---|
| 902 | | w = fabs(dfx); |
|---|
| 903 | | h = fabs(dfy); |
|---|
| 904 | | error += rs_max(w, h); |
|---|
| 905 | | if (error >= minSegLen) // add segment only if we have reached threshold length |
|---|
| 906 | | { |
|---|
| 907 | | // line to current |
|---|
| 908 | | LineTo(fx, fy); |
|---|
| 909 | | error = 0.0; |
|---|
| 910 | | } |
|---|
| 911 | | } |
|---|
| 912 | | |
|---|
| 913 | | LineTo(px4, py4); |
|---|
| 914 | | } |
|---|
| 915 | | |
|---|
| 916 | | |
|---|
| 917 | | void LineBuffer::TessellateQuadTo(double px1, double py1, double px2, double py2, double px3, double py3) |
|---|
| 918 | | { |
|---|
| 919 | | // We will base the max number of segments to use for approximation |
|---|
| 920 | | // on the bounds of the full line buffer contents. |
|---|
| 921 | | // TODO: as an improvement we could take the bounds of this particular curve |
|---|
| 922 | | // with respect to the full bounds of the line buffer data. |
|---|
| 923 | | double w = m_bounds.width(); |
|---|
| 924 | | double h = m_bounds.height(); |
|---|
| 925 | | double maxdim = rs_max(w, h); |
|---|
| 926 | | |
|---|
| 927 | | // minimum length of tessellation segment set to 1/100 of the bounds |
|---|
| 928 | | double minSegLen = maxdim * 0.01; |
|---|
| 929 | | |
|---|
| 930 | | /* |
|---|
| 931 | | // if we know the pixels per mapping unit ratio, we can use |
|---|
| 932 | | // this code to determine how many times at most |
|---|
| 933 | | // we want to loop in the forward difference loop |
|---|
| 934 | | double invscale = 1.0 / pixelsperunit; |
|---|
| 935 | | double dt = rs_max(1.0e-2, invscale / diff); // * m_invscale; |
|---|
| 936 | | */ |
|---|
| 937 | | |
|---|
| 938 | | // but for now we will iterate 100 times |
|---|
| 939 | | double dt = INV_TESSELLATION_ITERATIONS; |
|---|
| 940 | | |
|---|
| 941 | | double dt2 = dt*dt; |
|---|
| 942 | | |
|---|
| 943 | | double ax = px1 - 2.0*px2 + px3; // replace 2* by addition? |
|---|
| 944 | | double ay = py1 - 2.0*py2 + py3; // replace 2* by addition? |
|---|
| 945 | | |
|---|
| 946 | | double bx = 2.0*(px2-px1); |
|---|
| 947 | | double by = 2.0*(py2-py1); |
|---|
| 948 | | |
|---|
| 949 | | double fx = px1; |
|---|
| 950 | | double fy = py1; |
|---|
| 951 | | double dfx = bx*dt + ax*dt2; |
|---|
| 952 | | double dfy = by*dt + ay*dt2; |
|---|
| 953 | | double ddfx = 2.0*ax*dt2; |
|---|
| 954 | | double ddfy = 2.0*ay*dt2; |
|---|
| 955 | | |
|---|
| 956 | | double error = 0.0; |
|---|
| 957 | | |
|---|
| 958 | | //forward differencing loop |
|---|
| 959 | | int tMax = (int)(1.0/dt - 0.5); |
|---|
| 960 | | for (int t=0; t<tMax; ++t) |
|---|
| 961 | | { |
|---|
| 962 | | fx += dfx; |
|---|
| 963 | | fy += dfy; |
|---|
| 964 | | dfx += ddfx; |
|---|
| 965 | | dfy += ddfy; |
|---|
| 966 | | |
|---|
| 967 | | // slow but accurate |
|---|
| 968 | | // error += sqrt(dfx*dfx + dfy*dfy); |
|---|
| 969 | | |
|---|
| 970 | | // faster but less accurate error estimate |
|---|
| 971 | | w = fabs(dfx); |
|---|
| 972 | | h = fabs(dfy); |
|---|
| 973 | | error += rs_max(w, h); |
|---|
| 974 | | |
|---|
| 975 | | if (error >= minSegLen) // how many pixels should each line be? |
|---|
| 976 | | { |
|---|
| 977 | | LineTo(fx, fy); |
|---|
| 978 | | error = 0.0; |
|---|
| 979 | | } |
|---|
| 980 | | } |
|---|
| 981 | | |
|---|
| 982 | | LineTo(px3, py3); |
|---|