【问题标题】:Scaling sketches to different resolutions将草图缩放到不同的分辨率
【发布时间】:2019-10-27 08:54:33
【问题描述】:

现在这似乎是一个愚蠢的问题,但请听我说完。

直到现在,每当我想创建一个可以在多个分辨率上运行的草图时,我都会放置某些对象,例如 ellipse() 上的 width/2, height/2, width/16, width/16 来制作一个圆圈(是的,我知道有一个圆圈功能但这不是重点)。

但是,当创建包含一些细节的更精确的对象时,必须快速执行 width/2.489 或使用 width/2 - someNumber 可以达到相同效果的任何事情会变得非常烦人

假设我正在绘制一个Duck 对象,我想在xy 上显示我希望能够拥有Duckdraw() 方法列表,例如circle(x,y, 100); circle(x + someNum, y, 25);

我可以选择标准尺寸,比如int STD = width / 100,然后通过像circle(x + STD * 3, y, 25); 这样的方式添加这些小细节,但这也感觉不雅。

我知道 Processing 有一个 scale() 选项,但不幸的是,无法仅缩放正在绘制的对象的 heightswidths。它也缩放了绘制的位置。

所以,如果我先在 size(1920/2 * 1080/2) 上画一个草图,然后在 width / 2, height / 2 上画一个圆圈,然后在 size(1920,1080)scale(2) 上运行草图,它会在 width / 2 * 2 上画一个圆圈,然后把圆圈放在几乎不在屏幕上

那么...为不同分辨率绘制详细对象的最优雅方法是什么? (但纵横比相同)

【问题讨论】:

    标签: java processing


    【解决方案1】:

    几年前我做过类似的事情。我的太复杂了,无法分享代码,但我很乐意分享原理。

    为了实现这一点,我将我想在草图中显示的所有内容都基于 Drawable 基类,并使用 Render 方法来绘制它们。由于所有东西都将继承这种“一刀切”的方法,因此我可以实现一种在绘制时调整大小和替换所有东西的方法。

    所以我决定采用“官方”分辨率,即一切都是真实的比例,在绘制时我的基类会更正每个对象的位置和比例,而我不必做该死的东西,一旦那部分被编码过一次。

    这是这个逻辑的一个非常简化的实现:

    我们从一个全局变量开始,以同样的方式扩展所有内容:

    final float scaleNum = 3.0;
    

    我使用了一个漂亮的 ENUM 来决定(尚未编写的)Drawable 类将如何选择如何呈现 Drawable。为此,您必须在 fileName.java 文件中声明枚举,因为 Processing 不喜欢枚举,因此您必须依靠 java 来处理这个。如果你不想这样做,你可以使用其他任何东西,比如一个非常简单的字符串,如果你愿意的话。这是我的枚举:

    //In Enums.java
    enum Shape
    {
      RECTANGLE,
      TRIANGLE,
      ELLIPSE
    };
    

    然后我们为所有可绘制对象编写一个基类。这个设计仅用于处理少数操作可能性,但您似乎能够轻松地整理出此类细节。

    class Drawable {
      PVector position;
      boolean isVisible;
      float w, h;
      color fill = color(255,255,255);
      color stroke = color(255,255,255);
      float strokeWeight = 1;
      int shape;
    
      public Drawable (int shape) {
        this.shape = shape;
        position = new PVector(0,0);
        isVisible = true;
      }
    
      public void Render(){
        if (isVisible) {
          stroke(this.stroke);
          strokeWeight(this.strokeWeight);
          fill(this.fill);
          switch (shape) {
            case RECTANGLE:
              rect(position.x * scaleNum , position.y * scaleNum , w * scaleNum , h * scaleNum );
              break;
            case TRIANGLE:
              //code to draw a triangle at scale
              break;
            case ELLIPSE:
              //code to draw an ellipsis at scale
              break;
          }        
        }
      }
    }
    

    现在,这是一个继承 Drawable 类的类的示例:

    class Paddle extends Drawable{
      PVector speed; // some class-specific variable
    
      Paddle (float positionX, float positionY) {
        super(RECTANGLE);  // this calls the parent class Constructor
    
        speed = new PVector(5, 5); //initializing stuff
        position = new PVector (positionX, positionY);
      }
    
      public void Move() { //class-specific method. Notice how it doesn't need to be scaled.
        position.x += speed.x;
        position.y += speed.y;
      }
    }
    

    现在,作为一项额外功能,由于所有可绘制对象都继承了相同的Drawable 类,因此您可以创建所有可绘制对象的列表,并将您想要绘制的任何内容添加到其中并让它自行整理,而不是对所有内容进行微观管理在你的程序中。我的意思是,如果你有很多东西要画,那就太棒了:

    ArrayList <Drawable> myDrawables;
    
    void setup () {
      myDrawables = new Arraylist <Drawables>;
      myDrawable.add(new Paddle(0, 0));
      Hero myHeroicCharacter = new Hero();
      myDrawables.add(myHeroicCharacter);
      myDrawable.add(new anotherClass(someVariables));
      //etc.  
    }
    
    void draw () {
      for (Drawable drawable : myDrawables) {
        drawable.Render();
      }
    }
    

    我不认为客观上这是最好的方法,但它肯定是一种非常简单和易于管理的方法。玩得开心!

    【讨论】:

    • 有趣的方法如果我开始大型项目,我可能会研究更多。然而,这并不完全是我想要的。
    【解决方案2】:

    您的目标是获取任意坐标(我将其称为“世界空间”)并将它们映射到“屏幕空间”(显示器上的像素)。最好的方法确实是使用scale();但是,在您的代码中,您使用了scale(2)(从世界空间映射到更大的世界空间),然后您使用width / 2, height / 2(通常位于您的屏幕空间,但由于您之前按 2 缩放,因此您的 width / 2height / 2 将乘以 2)。

    相反,您应该使用scale() 从世界空间映射到屏幕空间,并使用世界空间坐标绘制对象。示例:

    // World space of 400 x 400, chosen arbitrarily
    final float worldWidth = 400.0
      , worldHeight = 400.0;
    
    void draw() {
      // Map the world space (of 400 x 400) to screen space
      /* Another way of thinking about this:
           A value on the X scale of 0 to 400 is divided by 400 into the scale of 0.0 to 1.0,
           and then is multiplied by the actual width of the screen;
           Whatever fraction of the world space's width it was at,
           it's now at that same fraction of the screen space's width.
      */
      scale(width/worldWidth, height/worldHeight);
    
      // Convert mouse coordinates from screen space to world space
      /* Explanation:
       We need to convert mouse coordinates from screen space to world space
       if we want to use their values in world space (scale only affects drawing).
       mouseX and mouseY are only changed when the mouse is moved;
       If we converted on frames where they haven't been updated,
       the conversions would accumulate.
       pmouseX and pmouseY are set to mouseX and mouseY between draw() calls.
       */
      if (pmouseX != mouseX || pmouseY != mouseY) {
        mouseX = round(mouseX * worldWidth / width);
        mouseY = round(mouseY * worldHeight / height);
      }
    
      // Example drawing code:
      background(64);
      // Same as ellipse(width/2, height/2, width/16, height/16) when not scaling
      ellipse(200, 200, 25, 25);
      // Same as ellipse(mouseX, mouseY, width/16, height/16) when not scaling
      ellipse(mouseX, mouseY, 25, 25);
    }
    

    如果你想修正屏幕比例,这样当你画一个宽度和高度相等的东西时,无论你的屏幕比例是多少,它都会变成一个正方形,你可以这样做:

    // World space of 1600 x 900 (16:9), chosen arbitrarily
    final float worldWidth = 1600.0
      , worldHeight = 900.0;
    
    void draw() {
      // Uniformly scale the world space so that it just fits the screen
      float toScreenScale = min(width/worldWidth, height/worldHeight);
      // Or: Uniformly scale the world space so that the screen just fits inside it
      // float toScreenScale = max(width/worldWidth, height/worldHeight);
      // Draw in the middle of the borders (when using min for toScreenScale)
      float xBorder = (width - worldWidth * toScreenScale) / 2.0;
      float yBorder = (height - worldHeight * toScreenScale) / 2.0;
      translate(xBorder, yBorder);
      // Scale world space uniformly
      scale(toScreenScale);
    
      // Convert mouse coordinates from screen space to world space
      if (pmouseX != mouseX || pmouseY != mouseY) {
        mouseX = round( (mouseX - xBorder) / toScreenScale );
        mouseY = round( (mouseY - yBorder) / toScreenScale );
      }
    
      // Example drawing code:
      background(64);
      stroke(0);
      fill(255);
      ellipse(800, 450, 56.25, 56.25);
      ellipse(mouseX, mouseY, 56.25, 56.25);
    
      // Draw border to avoid spill-out (when using min for toScreenScale)
      noStroke();
      fill(0);
      resetMatrix();
      // Top
      rect(0, 0, width, yBorder);
      // Bottom
      rect(0, height, width, -yBorder);
      // Left
      rect(0, 0, xBorder, height);
      // Right
      rect(width, 0, -xBorder, height);
    }
    

    如果您的世界空间和屏幕空间的比例不同,这将为您提供边框。如果您不手动填充这些边框(使用rect()您绘制完其他所有内容之后),可能会溢出到边框中。如果您将max 用于toScreenScale,它将切断而不是边界。


    注意事项:

    • 当我们将mouseXmouseY缩放到大于屏幕空间的世界空间时,舍入会使鼠标坐标不准确;相反,您最好制作一对新的float 坐标(或PVector),但您需要用该新变量替换代码中所有出现的mouseXmouseY

    没有愚蠢的问题

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多