Scaling a custom PShape without changing line thickness

I am teaching myself to code through coding games, I am currently working on asteroids.

I have a custom PShape called ast that uses a fair bit of vertices.

I create a separate instance of it called n in the asteroid class by doing this:

    n = ast;
    n = createShape(GROUP);
    n.addChild(ast);

Currently, I am attempting to scale them down by doing n.scale(1/2) in the constructor of smaller asteroids.

Due to the scaling, the asteroids end up having thinner lines, which is undesired.

I have attempted to create a line thickness system, but it seems that custom PShapes are unaffected by strokeWeight().

How do I fix this?
I am new to processing, so sorry in advance for not knowing too much

EDIT:
I decided to give up, and just make a class.
For anyone in the future, here is what I did:

public class ast{
  private PShape ast;
  public ast(float scl){
    float a = scl*SF;
    ast = createShape();
    ast.beginShape();
    //VERTEXs HERE
    ast.endShape(CLOSE);
  }
  public void display(float x, float y){
    shape(ast, x, y);
  }
}
2 Likes

Hi, I saw your message before you deleted it and looked into pushMatrix and popMatrix stuff.

I attempted to implement the matrix scaling that you mentioned by implementing the following in my asteroid class’s draw method

    pushMatrix();
    scale(scl);
    shape(n,x,y);
    popMatrix();

EDIT: I figured out the problem with my implementation, but it still has the initial problem of the line thickness.

EDIT 2: along with this, it causes misalignment between the hitboxes and the drawn object

try to setStrokeWeight as inverse of scaling factor
this will fix the thickness problem

PShape square;
void setup() {  
  fullScreen();
  stroke(255, 0,0);
  rectMode(CENTER);
  square = createShape(RECT, 0, 0, 80, 80);
  squareX = width/2;
  squareY = height/2;
}
float scale;
float thickness = 2;
float squareX, squareY;
void draw() {
  background(255); 
  pushMatrix();
  translate(squareX, squareY);
  scale(scale);
  square.setStrokeWeight(thickness * (1/scale));  //inverted
  shape(square, 0, 0);
  scale = map(mouseX, 0, width, 0, 10);
  popMatrix();
}

I havent tested the code, im on phone, but it should can be run

2 Likes

As a general rule, after a PShape has been created, we need to use methods which start w/ “set”, like setStrokeWeight(). :face_with_monocle:

However, a strokeWeight() acts as the default style for all PShape objects which would be created. :sunglasses:

2 Likes

I attempted to implement your solution, but it seems that CUSTOM PShapes don’t work with any stroke weight commands.

In my asteroid class’s display method I did the following to implement your suggestion:

 n.setStrokeWeight(1/scale);
    shape(n,x,y);

This didn’t work, so I replaced the above code with this to test if it was even doing anything.

    n.setStrokeWeight(20);
    shape(n,x,y);

if the command were working, all asteroids would be drawn with a stroke weight of 20, but they aren’t.


instead of that, they still have a stroke weight of 1, shown above with ellipses that have a weight of 1 for scale.

is this a problem with my implementation?

Let us see the full working code when the shape being drawn onto the screen

I was going to write a cut down code segment to do exactly that, but was unable to recreate what was happening.

what is the best way to share a multi-file sketch?

Copy everything into one ! And post.

1 Like

For the class-based solution, if you want the class object to scale multiple times – for example, making it shrink each time it was hit – you could take creating the PShape out of the constructor and recreate it when needed. While you can loop over the points of simple PShapes, it is often much easier to recreate a PShape than it is to edit it.

1 Like

This is pretty long but /shrug

void setup() {
  size(1060, 800);
  SF = width/1920.0;
  W = width;
  H = height;
  smooth(8);
  
  fontSetup();
  shapeSetup();
  gameSetup();
}

void draw() {
  gameHandle();
}

void gameHandle() {
  stepHandle();
  drawHandle();
}

public class asteroid{
  private float x,y,rot, r;
  private int size;
  private ast n;
  
  public asteroid(){
    
    x = random(-10, width+10);
    y = random(-10, height+10);
    rot = random(0, TWO_PI);
    size = 2;
    r = 180*SF;
    n = new ast(1);
  }
  public asteroid(float x_, float y_, float rot_, int size_){
    x = x_;
    y = y_;
    rot = rot_;
    size = size_;
    if (size == 1){
      n = new ast(.5);
      r = 90*SF;
    }
    if (size == 0){
      n = new ast(.25);
      r = 45*SF;
    }
  }
  
  public void step(){
    x+=(sin(rot)*astV);
    y+=(cos(rot)*astV);
    edgeHandle();
  }
  public void display(){
    n.display(x,y);
  }
  
  private void edgeHandle(){
    if(x>width+10)
      x = -10;
    if(x<-10)
      x = width+10;
    if(y>height+10)
      y = -10;
    if(y<-10)
      y = height+10;
  }
  
  public void hit(){
    if (size == 2)
      score +=20;
    if (size == 1)
      score +=50;
    if (size == 0)
      score +=100;
    this.step();
    if (!(size-1<0)){
      astList.add(new asteroid(x,y,rot+PI/6, size-1));
      astList.add(new asteroid(x,y,rot-PI/6, size-1));
    }
    astList.remove(this);
  }
  public float getX(){
    return x;
  }
  public float getY(){
    return y;
  }
  public float getR(){
    return r/2;
  }
}

public final float bulletSpeed = 12;
public bullet[] bulletList = new bullet[10];

public class bullet{
  private float rot;
  private float xVel, yVel;
  private float x, y;
  private int aliveT;
  public bullet(){
    x=-20;
    y=-20;
    rot=0;
    xVel=0;
    yVel=0;
    aliveT = 10000000;
  }
  public void ded(){
    x=-20;
    y=-20;
    rot=0;
    xVel=0;
    yVel=0;
    aliveT = 10000000;
  }
  public bullet(float x_, float y_, float rot_, float xVel_, float yVel_){
    x=x_;
    y=y_;
    rot=rot_;
    xVel=xVel_+(sin(rot)*bulletSpeed*SF);
    yVel=yVel_+(cos(rot)*bulletSpeed*SF);
    aliveT = 60;
  }
  public void display(){
    stroke(255);
    ellipse(x,y,5*SF,5*SF);
  }
  public void step(){
    x+=xVel;
    y-=yVel;
    edgeHandle();
    aliveT--;
    if (aliveT<=0)
      this.ded();
  }
  private void edgeHandle(){
    if(x>width+10)
      x = -10;
    if(x<-10)
      x = width+10;
    if(y>height+10)
      y = -10;
    if(y<-10)
      y = height+10;
  }
  public void colisT(){
    for (asteroid a : astList){
      double xDif = x - a.getX();
      double yDif = y - a.getY();
      double distanceSquared = xDif * xDif + yDif * yDif;
      if (distanceSquared < (2.5*SF + a.getR()) * (2.5*SF + a.getR())){
        a.hit();
        aliveT = 0;
        break;
      }
    }
  }
}

void keyPressed() {
  switch(key){
    case ESC:
      key = 0;
      break;
  }
  if (key == 'a' || keyCode == LEFT)
    p1.Left(true);
  if (key == 'd' || keyCode == RIGHT)
    p1.Right(true);
  if (key == 'w' || keyCode == UP)
    p1.Accl(true);
}
void keyReleased() {
  if (key == 'a' || keyCode == LEFT)
    p1.Left(false);
  if (key == 'd' || keyCode == RIGHT)
    p1.Right(false);
  if (key == 'w' || keyCode == UP)
    p1.Accl(false);
  if (key == ' ' || keyCode == UP)
    p1.fire();
}

public class ship {
  private float rotVel, rot, xVel, yVel;
  private float x, y;
  private int health, invT, bNum;
  private boolean inv, accl, rotL, rotR;
  
  public ship() {
    x = width/2.0;
    y = height/2.0;
    health = 3;
    invT = 0;
    bNum = 0;
    inv = false;
    accl = false;
    rotL = false;
    rotR = false;
  }
  public void colisT(){
    for (asteroid a : astList){
      double xDif = x - a.getX();
      double yDif = y - a.getY();
      double distanceSquared = xDif * xDif + yDif * yDif;
      if (distanceSquared < (10 + a.getR()) * (10 + a.getR())){
        this.hit();
      }
    }
  }
  public void display() {
    if (!inv || invT%15==0)
      shape(pShip, x, y);
    if (health>=1){
      if (health >= 2){
        if (health == 3){
          shape(hShip, 80, 45);
        }
        shape(hShip, 50, 45);
      }
      shape(hShip, 20, 45);
    }
  }
  public void step() {
    movHandle();
    edgeHandle();
    colisT();
    if (invT>0){
      inv = true;
      invT--;
    }
    if (invT<=0)
      inv = false;
  }
  public void hit(){
    if (!inv){
      invT = 120;
      health--;
      if (health<=0)
        gameOver();
    }
  }
  public void gameOver(){}
  
  public void fire(){
    bulletList[bNum] = new bullet(x,y,rot,xVel,yVel);
    bNum++;
    if (bNum>=10)
      bNum = 0;
  }
  
  //---------------------------MOVEMENT HANDLING-----------------------------
  private void movHandle() {
    rotHandle();
    shiftHandle();
  }
  private void rotHandle() {
    if (rotVel>-0.001&&rotVel<0.001)
      rotVel=0;
      
    if (rotVel>0.00)
      rotVel-=0.001;
    if (rotVel<-0.00)//rotation decay
      rotVel+=0.001;
      
    if (rotVel<-0.12)
      rotVel=-0.12;
    if (rotVel>0.12)//max turn speed
      rotVel=0.12;
      
    if (rot>=TWO_PI||rot<=-TWO_PI)//overflow protection
      rot=0;
    pShip.rotate(-rot);
    rot+=rotVel;
    pShip.rotate(rot);
  }
  private void shiftHandle(){
    if (accl){
      xVel += sin(rot)*AcclR8;
      yVel += cos(rot)*AcclR8;
    }
    if (rotR){
      rotVel+=rotSpeed;
    }
    if (rotL){
      rotVel-=rotSpeed;
    }
    x+=xVel;
    y+=-yVel;
  }
  private void edgeHandle(){
    if(x>width+10)
      x = -10;
    if(x<-10)
      x = width+10;
    if(y>height+10)
      y = -10;
    if(y<-10)
      y = height+10;
  }
  public void Right(boolean a){
    rotR = a;
  }
  public void Left(boolean a){
    rotL = a;
  }
  public void Accl(boolean a){
    accl = a;
    if (!a){
      yVel = yVel*2/3;
      xVel = xVel*2/3;
    }
  }
}

public float SF;
public PShape ship, pShip, hShip;
public float W, H, score;
public PFont large, small;
public ArrayList<asteroid> astList = new ArrayList();
public final float astV = 1;
public float level;
public final float rotSpeed = 0.002;
public final float AcclR8 = 0.1;
public ship p1;

public void fontSetup(){
  small = createFont("Hyperspace.otf", 30);
  large = createFont("Hyperspace.otf", 50);
}
public void shapeSetup(){
  noFill();
  ship = createShape();
  ship.beginShape();
  ship.noFill();
  ship.stroke(255);
  ship.vertex(0, -25*SF);
  ship.vertex(20*SF, 25*SF);
  ship.vertex(0, 15*SF);
  ship.vertex(-20*SF, 25*SF);
  ship.endShape(CLOSE);
  pShip = ship;
  hShip = ship;
  pShip = createShape(GROUP);
  pShip.addChild(ship);
  hShip = createShape(GROUP);
  hShip.addChild(ship);
}
public class ast{
  private PShape ast;
  public ast(float scl){
    float a = scl*SF;
    ast = createShape();
    ast.beginShape();
    ast.noFill();
    ast.stroke(255);
    ast.vertex(-20*a,30*a);
    ast.vertex(-30*a,90*a);
    ast.vertex(15*a,90*a);
    ast.vertex(90*a,20*a);
    ast.vertex(90*a,-20*a);
    ast.vertex(30*a,-90*a);
    ast.vertex(-30*a,-90*a);
    ast.vertex(-90*a,-40*a);
    ast.vertex(-70*a,-20*a);
    ast.vertex(-90*a,0);
    ast.vertex(-60*a,70*a);
    ast.endShape(CLOSE);
    ast.rotate(random(0,TWO_PI));
  }
  public void display(float x, float y){
    shape(ast, x, y);
  }
}
public void gameSetup(){
  score = 0;
  level = 0;
  p1 = new ship();
  for (int i = 0; i<10; i++) {
    bulletList[i] = new bullet();
  }
}

public void stepHandle(){
  astHandle();
  p1.step();
  p1.colisT();
  for (bullet b : bulletList) {
    b.step();
    b.colisT();
  }
  for (asteroid a : astList) {
    a.step();
  }
}
public void drawHandle(){
  background(0);
  p1.display();
  for (bullet b : bulletList){
    b.display();
  }
  for (asteroid a : astList){
    a.display();
  }
  textFont(small);
  text((int)score, 15, 27.25);
}

public void astHandle(){
  if (astList.size()==0){
    for (int i = 0; i < 4+level; i++){
      astList.add(new asteroid());
    }
    level++;
  }
}

if anyone looks back on this thread looking for an answer

  1. do something realy weird with push/pop-matrix
  2. create new pshapes for each scale of object, make a function to automate their creation if you want.

Sorry for bumping the topic to the top of the list after a year, I just dont like leaving questions unsolved.

1 Like