绘制弧线并对其进行渐变

5
我想知道是否可以在图形面板上使用渐变绘制弧形,以及如何实现。我的最终目标是将弧形旋转一整圈,类似于旋转加载圆圈,但它不是加载栏。它将成为自定义JButton的背景。如果有任何建议可以创建类似效果的替代方案,将不胜感激。这类似于我想要绘制的内容。请记住,它将会“旋转”。 enter image description here

@MadProgrammer 抱歉,我刚意识到我并没有真正询问如何做...问题已更新。 - Exikle
2
给了我时间来准备一个例子 ;) - MadProgrammer
1个回答

9

enter image description here

public class TestArc {

    public static void main(String[] args) {
        new TestArc();
    }

    public TestArc() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }

        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            int radius = Math.min(getWidth(), getHeight());
            int x = (getWidth() - radius) / 2;
            int y = (getHeight() - radius) / 2;

            RadialGradientPaint rgp = new RadialGradientPaint(
                            new Point(getWidth() / 2, getHeight() / 2),
                            radius,
                            new float[]{0f, 1f},
                            new Color[]{Color.RED, Color.YELLOW}
                            );
            g2d.setPaint(rgp);
            g2d.fill(new Arc2D.Float(x, y, radius, radius, 0, 45, Arc2D.PIE));
            g2d.dispose();
        }

    }

}

您可能想了解有关2D图形的更多信息,请查看2D Graphics在获得额外输入后更新 所以您需要一个圆锥填充效果...
我使用的实现来自Harmonic Code,但我找不到直接引用它的地方(我认为它是他系列教程(非常出色)的一部分),但您可以在此处查看源代码here
现在。 我遇到了角度问题,因为似乎0从顶点(而不是左边)开始,并且不喜欢负角度...您可能会有更好的运气,但我所做的是在我可以轻松工作的位置创建基本缓冲区,然后使用AffineTransformation旋转图形上下文...
public class TestArc {

  public static void main(String[] args) {
    new TestArc();
  }

  public TestArc() {
    EventQueue.invokeLater(new Runnable() {
      @Override
      public void run() {
        try {
          UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
        }

        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.add(new TestPane());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
      }
    });
  }

  public class TestPane extends JPanel {

    private float angle = 0;
    private float extent = 270;
    private BufferedImage buffer;

    public TestPane() {
      Timer timer = new Timer(125, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
          angle -= 5;
          if (angle > 360) {
            angle = 0;
          }
          repaint();
        }
      });
      timer.setRepeats(true);
      timer.setCoalesce(false);
      timer.start();
    }

    @Override
    public Dimension getPreferredSize() {
      return new Dimension(200, 200);
    }

    protected BufferedImage getBuffer() {

      if (buffer == null) {
        int radius = Math.min(getWidth(), getHeight());
        int x = (getWidth() - radius) / 2;
        int y = (getHeight() - radius) / 2;
        buffer = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = buffer.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        float startAngle = 0;
        Color start = new Color(0, 128, 0, 128);
        Color end = new Color(0, 128, 0, 0);
        ConicalGradientPaint rgp = new ConicalGradientPaint(
            true,
            new Point(getWidth() / 2, getHeight() / 2),
            0.5f,
            new float[]{startAngle, extent},
            new Color[]{start, end});
        g2d.setPaint(rgp);
        g2d.fill(new Arc2D.Float(x, y, radius, radius, startAngle + 90, -extent, Arc2D.PIE));
//      g2d.fill(new Ellipse2D.Float(0, 0, radius, radius));
        g2d.dispose();
        g2d.dispose();
      }

      return buffer;
    }

    protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      Graphics2D g2d = (Graphics2D) g.create();
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

      int radius = Math.min(getWidth(), getHeight());
      int x = (getWidth()) / 2;
      int y = (getHeight()) / 2;
      BufferedImage buffer = getBuffer();
      g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(angle), x, y));
      x = (getWidth() - buffer.getWidth()) / 2;
      y = (getHeight() - buffer.getHeight()) / 2;
      g2d.drawImage(buffer, x, y, this);
      g2d.dispose();
    }
  }

  public final class ConicalGradientPaint implements java.awt.Paint {

    private final java.awt.geom.Point2D CENTER;
    private final double[] FRACTION_ANGLES;
    private final double[] RED_STEP_LOOKUP;
    private final double[] GREEN_STEP_LOOKUP;
    private final double[] BLUE_STEP_LOOKUP;
    private final double[] ALPHA_STEP_LOOKUP;
    private final java.awt.Color[] COLORS;
    private static final float INT_TO_FLOAT_CONST = 1f / 255f;

    /**
     * Standard constructor which takes the FRACTIONS in values from 0.0f to
     * 1.0f
     *
     * @param CENTER
     * @param GIVEN_FRACTIONS
     * @param GIVEN_COLORS
     * @throws IllegalArgumentException
     */
    public ConicalGradientPaint(final java.awt.geom.Point2D CENTER, final float[] GIVEN_FRACTIONS, final java.awt.Color[] GIVEN_COLORS) throws IllegalArgumentException {
      this(false, CENTER, 0.0f, GIVEN_FRACTIONS, GIVEN_COLORS);
    }

    /**
     * Enhanced constructor which takes the FRACTIONS in degress from 0.0f to
     * 360.0f and also an GIVEN_OFFSET in degrees around the rotation CENTER
     *
     * @param USE_DEGREES
     * @param CENTER
     * @param GIVEN_OFFSET
     * @param GIVEN_FRACTIONS
     * @param GIVEN_COLORS
     * @throws IllegalArgumentException
     */
    public ConicalGradientPaint(final boolean USE_DEGREES, final java.awt.geom.Point2D CENTER, final float GIVEN_OFFSET, final float[] GIVEN_FRACTIONS, final java.awt.Color[] GIVEN_COLORS) throws IllegalArgumentException {
      // Check that fractions and colors are of the same size
      if (GIVEN_FRACTIONS.length != GIVEN_COLORS.length) {
        throw new IllegalArgumentException("Fractions and colors must be equal in size");
      }

      final java.util.ArrayList<Float> FRACTION_LIST = new java.util.ArrayList<Float>(GIVEN_FRACTIONS.length);
      final float OFFSET;
      if (USE_DEGREES) {
        final double DEG_FRACTION = 1f / 360f;
        if (Double.compare((GIVEN_OFFSET * DEG_FRACTION), -0.5) == 0) {
          OFFSET = -0.5f;
        } else if (Double.compare((GIVEN_OFFSET * DEG_FRACTION), 0.5) == 0) {
          OFFSET = 0.5f;
        } else {
          OFFSET = (float) (GIVEN_OFFSET * DEG_FRACTION);
        }
        for (float fraction : GIVEN_FRACTIONS) {
          FRACTION_LIST.add((float) (fraction * DEG_FRACTION));
        }
      } else {
        // Now it seems to work with rotation of 0.5f, below is the old code to correct the problem
//            if (GIVEN_OFFSET == -0.5)
//            {
//                // This is needed because of problems in the creation of the Raster
//                // with a angle offset of exactly -0.5
//                OFFSET = -0.49999f;
//            }
//            else if (GIVEN_OFFSET == 0.5)
//            {
//                // This is needed because of problems in the creation of the Raster
//                // with a angle offset of exactly +0.5
//                OFFSET = 0.499999f;
//            }
//            else
        {
          OFFSET = GIVEN_OFFSET;
        }
        for (float fraction : GIVEN_FRACTIONS) {
          FRACTION_LIST.add(fraction);
        }
      }

      // Check for valid offset
      if (OFFSET > 0.5f || OFFSET < -0.5f) {
        throw new IllegalArgumentException("Offset has to be in the range of -0.5 to 0.5");
      }

      // Adjust fractions and colors array in the case where startvalue != 0.0f and/or endvalue != 1.0f
      final java.util.List<java.awt.Color> COLOR_LIST = new java.util.ArrayList<java.awt.Color>(GIVEN_COLORS.length);
      COLOR_LIST.addAll(java.util.Arrays.asList(GIVEN_COLORS));

      // Assure that fractions start with 0.0f
      if (FRACTION_LIST.get(0) != 0.0f) {
        FRACTION_LIST.add(0, 0.0f);
        final java.awt.Color TMP_COLOR = COLOR_LIST.get(0);
        COLOR_LIST.add(0, TMP_COLOR);
      }

      // Assure that fractions end with 1.0f
      if (FRACTION_LIST.get(FRACTION_LIST.size() - 1) != 1.0f) {
        FRACTION_LIST.add(1.0f);
        COLOR_LIST.add(GIVEN_COLORS[0]);
      }

      // Recalculate the fractions and colors with the given offset
      final java.util.Map<Float, java.awt.Color> FRACTION_COLORS = recalculate(FRACTION_LIST, COLOR_LIST, OFFSET);

      // Clear the original FRACTION_LIST and COLOR_LIST
      FRACTION_LIST.clear();
      COLOR_LIST.clear();

      // Sort the hashmap by fraction and add the values to the FRACION_LIST and COLOR_LIST
      final java.util.SortedSet<Float> SORTED_FRACTIONS = new java.util.TreeSet<Float>(FRACTION_COLORS.keySet());
      final java.util.Iterator<Float> ITERATOR = SORTED_FRACTIONS.iterator();
      while (ITERATOR.hasNext()) {
        final float CURRENT_FRACTION = ITERATOR.next();
        FRACTION_LIST.add(CURRENT_FRACTION);
        COLOR_LIST.add(FRACTION_COLORS.get(CURRENT_FRACTION));
      }

      // Set the values
      this.CENTER = CENTER;
      COLORS = COLOR_LIST.toArray(new java.awt.Color[]{});

      // Prepare lookup table for the angles of each fraction
      final int MAX_FRACTIONS = FRACTION_LIST.size();
      this.FRACTION_ANGLES = new double[MAX_FRACTIONS];
      for (int i = 0; i < MAX_FRACTIONS; i++) {
        FRACTION_ANGLES[i] = FRACTION_LIST.get(i) * 360;
      }

      // Prepare lookup tables for the color stepsize of each color
      RED_STEP_LOOKUP = new double[COLORS.length];
      GREEN_STEP_LOOKUP = new double[COLORS.length];
      BLUE_STEP_LOOKUP = new double[COLORS.length];
      ALPHA_STEP_LOOKUP = new double[COLORS.length];

      for (int i = 0; i < (COLORS.length - 1); i++) {
        RED_STEP_LOOKUP[i] = ((COLORS[i + 1].getRed() - COLORS[i].getRed()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
        GREEN_STEP_LOOKUP[i] = ((COLORS[i + 1].getGreen() - COLORS[i].getGreen()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
        BLUE_STEP_LOOKUP[i] = ((COLORS[i + 1].getBlue() - COLORS[i].getBlue()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
        ALPHA_STEP_LOOKUP[i] = ((COLORS[i + 1].getAlpha() - COLORS[i].getAlpha()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
      }
    }

    /**
     * Recalculates the fractions in the FRACTION_LIST and their associated
     * colors in the COLOR_LIST with a given OFFSET. Because the conical
     * gradients always starts with 0 at the top and clockwise direction you
     * could rotate the defined conical gradient from -180 to 180 degrees which
     * equals values from -0.5 to +0.5
     *
     * @param FRACTION_LIST
     * @param COLOR_LIST
     * @param OFFSET
     * @return Hashmap that contains the recalculated fractions and colors after
     * a given rotation
     */
    private java.util.HashMap<Float, java.awt.Color> recalculate(final java.util.List<Float> FRACTION_LIST, final java.util.List<java.awt.Color> COLOR_LIST, final float OFFSET) {
      // Recalculate the fractions and colors with the given offset
      final int MAX_FRACTIONS = FRACTION_LIST.size();
      final java.util.HashMap<Float, java.awt.Color> FRACTION_COLORS = new java.util.HashMap<Float, java.awt.Color>(MAX_FRACTIONS);
      for (int i = 0; i < MAX_FRACTIONS; i++) {
        // Add offset to fraction
        final float TMP_FRACTION = FRACTION_LIST.get(i) + OFFSET;

        // Color related to current fraction
        final java.awt.Color TMP_COLOR = COLOR_LIST.get(i);

        // Check each fraction for limits (0...1)
        if (TMP_FRACTION <= 0) {
          FRACTION_COLORS.put(1.0f + TMP_FRACTION + 0.0001f, TMP_COLOR);

          final float NEXT_FRACTION;
          final java.awt.Color NEXT_COLOR;
          if (i < MAX_FRACTIONS - 1) {
            NEXT_FRACTION = FRACTION_LIST.get(i + 1) + OFFSET;
            NEXT_COLOR = COLOR_LIST.get(i + 1);
          } else {
            NEXT_FRACTION = 1 - FRACTION_LIST.get(0) + OFFSET;
            NEXT_COLOR = COLOR_LIST.get(0);
          }
          if (NEXT_FRACTION > 0) {
            final java.awt.Color NEW_FRACTION_COLOR = getColorFromFraction(TMP_COLOR, NEXT_COLOR, (int) ((NEXT_FRACTION - TMP_FRACTION) * 10000), (int) ((-TMP_FRACTION) * 10000));
            FRACTION_COLORS.put(0.0f, NEW_FRACTION_COLOR);
            FRACTION_COLORS.put(1.0f, NEW_FRACTION_COLOR);
          }
        } else if (TMP_FRACTION >= 1) {
          FRACTION_COLORS.put(TMP_FRACTION - 1.0f - 0.0001f, TMP_COLOR);

          final float PREVIOUS_FRACTION;
          final java.awt.Color PREVIOUS_COLOR;
          if (i > 0) {
            PREVIOUS_FRACTION = FRACTION_LIST.get(i - 1) + OFFSET;
            PREVIOUS_COLOR = COLOR_LIST.get(i - 1);
          } else {
            PREVIOUS_FRACTION = FRACTION_LIST.get(MAX_FRACTIONS - 1) + OFFSET;
            PREVIOUS_COLOR = COLOR_LIST.get(MAX_FRACTIONS - 1);
          }
          if (PREVIOUS_FRACTION < 1) {
            final java.awt.Color NEW_FRACTION_COLOR = getColorFromFraction(TMP_COLOR, PREVIOUS_COLOR, (int) ((TMP_FRACTION - PREVIOUS_FRACTION) * 10000), (int) (TMP_FRACTION - 1.0f) * 10000);
            FRACTION_COLORS.put(1.0f, NEW_FRACTION_COLOR);
            FRACTION_COLORS.put(0.0f, NEW_FRACTION_COLOR);
          }
        } else {
          FRACTION_COLORS.put(TMP_FRACTION, TMP_COLOR);
        }
      }

      // Clear the original FRACTION_LIST and COLOR_LIST
      FRACTION_LIST.clear();
      COLOR_LIST.clear();

      return FRACTION_COLORS;
    }

    /**
     * With the START_COLOR at the beginning and the DESTINATION_COLOR at the
     * end of the given RANGE the method will calculate and return the color
     * that equals the given VALUE. e.g. a START_COLOR of BLACK (R:0, G:0, B:0,
     * A:255) and a DESTINATION_COLOR of WHITE(R:255, G:255, B:255, A:255) with
     * a given RANGE of 100 and a given VALUE of 50 will return the color that
     * is exactly in the middle of the gradient between black and white which is
     * gray(R:128, G:128, B:128, A:255) So this method is really useful to
     * calculate colors in gradients between two given colors.
     *
     * @param START_COLOR
     * @param DESTINATION_COLOR
     * @param RANGE
     * @param VALUE
     * @return Color calculated from a range of values by given value
     */
    public java.awt.Color getColorFromFraction(final java.awt.Color START_COLOR, final java.awt.Color DESTINATION_COLOR, final int RANGE, final int VALUE) {
      final float SOURCE_RED = START_COLOR.getRed() * INT_TO_FLOAT_CONST;
      final float SOURCE_GREEN = START_COLOR.getGreen() * INT_TO_FLOAT_CONST;
      final float SOURCE_BLUE = START_COLOR.getBlue() * INT_TO_FLOAT_CONST;
      final float SOURCE_ALPHA = START_COLOR.getAlpha() * INT_TO_FLOAT_CONST;

      final float DESTINATION_RED = DESTINATION_COLOR.getRed() * INT_TO_FLOAT_CONST;
      final float DESTINATION_GREEN = DESTINATION_COLOR.getGreen() * INT_TO_FLOAT_CONST;
      final float DESTINATION_BLUE = DESTINATION_COLOR.getBlue() * INT_TO_FLOAT_CONST;
      final float DESTINATION_ALPHA = DESTINATION_COLOR.getAlpha() * INT_TO_FLOAT_CONST;

      final float RED_DELTA = DESTINATION_RED - SOURCE_RED;
      final float GREEN_DELTA = DESTINATION_GREEN - SOURCE_GREEN;
      final float BLUE_DELTA = DESTINATION_BLUE - SOURCE_BLUE;
      final float ALPHA_DELTA = DESTINATION_ALPHA - SOURCE_ALPHA;

      final float RED_FRACTION = RED_DELTA / RANGE;
      final float GREEN_FRACTION = GREEN_DELTA / RANGE;
      final float BLUE_FRACTION = BLUE_DELTA / RANGE;
      final float ALPHA_FRACTION = ALPHA_DELTA / RANGE;
      //System.out.println(DISTANCE + "     " + CURRENT_FRACTION);

      return new java.awt.Color(SOURCE_RED + RED_FRACTION * VALUE, SOURCE_GREEN + GREEN_FRACTION * VALUE, SOURCE_BLUE + BLUE_FRACTION * VALUE, SOURCE_ALPHA + ALPHA_FRACTION * VALUE);
    }

    @Override
    public java.awt.PaintContext createContext(final java.awt.image.ColorModel COLOR_MODEL, final java.awt.Rectangle DEVICE_BOUNDS, final java.awt.geom.Rectangle2D USER_BOUNDS, final java.awt.geom.AffineTransform TRANSFORM, final java.awt.RenderingHints HINTS) {
      final java.awt.geom.Point2D TRANSFORMED_CENTER = TRANSFORM.transform(CENTER, null);
      return new ConicalGradientPaintContext(TRANSFORMED_CENTER);
    }

    @Override
    public int getTransparency() {
      return java.awt.Transparency.TRANSLUCENT;
    }

    private final class ConicalGradientPaintContext implements java.awt.PaintContext {

      final private java.awt.geom.Point2D CENTER;

      public ConicalGradientPaintContext(final java.awt.geom.Point2D CENTER) {
        this.CENTER = new java.awt.geom.Point2D.Double(CENTER.getX(), CENTER.getY());
      }

      @Override
      public void dispose() {
      }

      @Override
      public java.awt.image.ColorModel getColorModel() {
        return java.awt.image.ColorModel.getRGBdefault();
      }

      @Override
      public java.awt.image.Raster getRaster(final int X, final int Y, final int TILE_WIDTH, final int TILE_HEIGHT) {
        final double ROTATION_CENTER_X = -X + CENTER.getX();
        final double ROTATION_CENTER_Y = -Y + CENTER.getY();

        final int MAX = FRACTION_ANGLES.length;

        // Create raster for given colormodel
        final java.awt.image.WritableRaster RASTER = getColorModel().createCompatibleWritableRaster(TILE_WIDTH, TILE_HEIGHT);

        // Create data array with place for red, green, blue and alpha values
        int[] data = new int[(TILE_WIDTH * TILE_HEIGHT * 4)];

        double dx;
        double dy;
        double distance;
        double angle;
        double currentRed = 0;
        double currentGreen = 0;
        double currentBlue = 0;
        double currentAlpha = 0;

        for (int py = 0; py < TILE_HEIGHT; py++) {
          for (int px = 0; px < TILE_WIDTH; px++) {

            // Calculate the distance between the current position and the rotation angle
            dx = px - ROTATION_CENTER_X;
            dy = py - ROTATION_CENTER_Y;
            distance = Math.sqrt(dx * dx + dy * dy);

            // Avoid division by zero
            if (distance == 0) {
              distance = 1;
            }

            // 0 degree on top
            angle = Math.abs(Math.toDegrees(Math.acos(dx / distance)));

            if (dx >= 0 && dy <= 0) {
              angle = 90.0 - angle;
            } else if (dx >= 0 && dy >= 0) {
              angle += 90.0;
            } else if (dx <= 0 && dy >= 0) {
              angle += 90.0;
            } else if (dx <= 0 && dy <= 0) {
              angle = 450.0 - angle;
            }

            // Check for each angle in fractionAngles array
            for (int i = 0; i < (MAX - 1); i++) {
              if ((angle >= FRACTION_ANGLES[i])) {
                currentRed = COLORS[i].getRed() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * RED_STEP_LOOKUP[i];
                currentGreen = COLORS[i].getGreen() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * GREEN_STEP_LOOKUP[i];
                currentBlue = COLORS[i].getBlue() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * BLUE_STEP_LOOKUP[i];
                currentAlpha = COLORS[i].getAlpha() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * ALPHA_STEP_LOOKUP[i];
                continue;
              }
            }

            // Fill data array with calculated color values
            final int BASE = (py * TILE_WIDTH + px) * 4;
            data[BASE + 0] = (int) (currentRed * 255);
            data[BASE + 1] = (int) (currentGreen * 255);
            data[BASE + 2] = (int) (currentBlue * 255);
            data[BASE + 3] = (int) (currentAlpha * 255);
          }
        }

        // Fill the raster with the data
        RASTER.setPixels(0, 0, TILE_WIDTH, TILE_HEIGHT, data);

        return RASTER;
      }
    }
  }
}

虽然我标记为正确,但我意识到这不是我要找的,抱歉。我的弧是“C”形,因此它不是Arc2D.Pie,而是Arc2D.OPEN。我想要的是从弧的开始到弧的结束进行渐变。 - Exikle
@MadProgrammer 我不想从圆的中间开始并逐渐渗出。我希望它类似于...一个例子就是潜艇上的声纳。线如何在圆圈中旋转但有一个轨迹。我想要那个,但只在外面,因此使用Arc2D.OPEN。 - Exikle
@MadProgrammer 我在原始问题中放了一张图片作为我想要的示例。 - Exikle
1
@Exikle 我已经添加了一个使用圆锥填充效果的解决方案。将其应用于不同的形状不应该很困难。 - MadProgrammer
@MadProgrammer 谢谢,抱歉造成困惑。 - Exikle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接