简体   繁体   中英

Need help converting .PDE to P5.js

I'm new to both languages, and I'm trying to convert this program I found on github and edited, to p5.js so I can include it in a webpage. I tried following guides and replacing void() with function(), int i with var i etc.. but there seems to be something wrong. The first code is the original .pde and the second one is my attempt at converting it. Many thanks!

final int STAGE_WIDTH = 1200;
final int STAGE_HEIGHT = 950;
final int NB_PARTICLES = 60000;
final float MAX_PARTICLE_SPEED = 5;

final int MIN_LIFE_TIME = 20;
final int MAX_LIFE_TIME = 80;
final String IMAGE_PATH = "starrynight.jpg";

myVector tabParticles[];
float particleSize = 1.2;
PImage myImage;
int imageW;
int imageH;
color myPixels[];
FlowField ff;
GUI gui;

void setup()
{
  size(1200, 950, P3D);
  background(0);
  initializeImage();
  initializeParticles();
  ff = new FlowField(5);
  gui = new GUI(this);
  gui.setup();
}

void initializeImage()
{
  myImage = loadImage(IMAGE_PATH);
  imageW = myImage.width;
  imageH = myImage.height;
  myPixels = new color[imageW * imageH];
  myImage.loadPixels();
  myPixels = myImage.pixels;
  image(myImage, 0, 0);
}

void setParticle(int i) {
  tabParticles[i] = new myVector((int)random(imageW), (int)random(imageH));
  tabParticles[i].prevX = tabParticles[i].x;
  tabParticles[i].prevY = tabParticles[i].y;
  tabParticles[i].count = (int)random(MIN_LIFE_TIME, MAX_LIFE_TIME);
  tabParticles[i].myColor = myPixels[(int)(tabParticles[i].y)*imageW + (int)(tabParticles[i].x)];
}

void initializeParticles()
{
  tabParticles = new myVector[NB_PARTICLES];
  for (int i = 0; i < NB_PARTICLES; i++)
  {
    setParticle(i);
  }
}

void draw()
{
  ff.setRadius(gui.getR());
  ff.setForce(gui.getF());
  particleSize = gui.getS();
  float vx;
  float vy;
  PVector v;
  for (int i = 0; i < NB_PARTICLES; i++)
  {
    tabParticles[i].prevX = tabParticles[i].x;
    tabParticles[i].prevY = tabParticles[i].y;
    v = ff.lookup(tabParticles[i].x, tabParticles[i].y);
    vx = v.x;
    vy = v.y;
    vx = constrain(vx, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
    vy = constrain(vy, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
    tabParticles[i].x += vx;
    tabParticles[i].y += vy;
    tabParticles[i].count--;
    if ((tabParticles[i].x < 0) || (tabParticles[i].x > imageW-1) ||
      (tabParticles[i].y < 0) || (tabParticles[i].y > imageH-1) ||
      tabParticles[i].count < 0) {
      setParticle(i);
    }
    strokeWeight(1.5*particleSize);
    stroke(tabParticles[i].myColor, 250);
    line(tabParticles[i].prevX, tabParticles[i].prevY, tabParticles[i].x, tabParticles[i].y);
  }
  ff.updateField();
}

void mouseDragged() {
  if(mouseX>950 && mouseY>830) return;
  ff.onMouseDrag();
}

void keyPressed() {
  //if (key =='s' || key == 'S') {
  //  ff.saveField();
  //}
}

class myVector extends PVector
{
  myVector (float p_x, float p_y) {
    super(p_x, p_y);
  }
  float prevX;
  float prevY;
  int count;
  color myColor;
}



import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

class FlowField {
  PVector[][] field;
  PVector[][] tempField;
  int cols, rows;
  int resolution;
  int affectRadius;
  float force;
  File file = new File(dataPath("field.txt"));

  FlowField(int r) {
    resolution = r;
    cols = 1200 / resolution;
    rows = 950 / resolution;
    field = new PVector[cols][rows];
    tempField = new PVector[cols][rows];
    init();
    affectRadius = 3;
    force = 1;
  }

  void setRadius(int r) {
    affectRadius = r;
  }

  void setForce(float f) {
    force = f;
  }

  void init() {
    try { 
      for (int i=0; i<cols; i++) {
        for (int j=0; j<rows; j++) {
          tempField[i][j] = new PVector(0, 0);
        }
      }
      readField();
    }
    catch(Exception e) {
      for (int i=0; i<cols; i++) {
        for (int j=0; j<rows; j++) {
          field[i][j] = new PVector(0, 0);
        }
      }
    }
  }

  PVector lookup(float x, float y) {
    int column = int(constrain(x/resolution, 0, cols-1));
    int row = int(constrain(y/resolution, 0, rows-1));
    return PVector.add(field[column][row],tempField[column][row]);
  }

  void drawBrush() {
    pushStyle();
    noFill();
    stroke(255, 255, 255);
    ellipse(mouseX, mouseY, affectRadius*10, affectRadius*10);
    popStyle();
  }

  void drawField(float x, float y, PVector v) {
    int column = int(constrain(x/resolution, 0, cols-1));
    int row = int(constrain(y/resolution, 0, rows-1));
    for (int i=-affectRadius; i<=affectRadius; i++) {
      for (int j=-affectRadius; j<=affectRadius; j++) {
        if (i*i+j*j<affectRadius*affectRadius) {
          try { 
            tempField[column+i][row+j].add(v).mult(0.9);
          }
          catch(Exception e) {
          }
        }
      }
    }
  }
  
  void updateField(){
    for (int i=0; i<cols; i++) {
        for (int j=0; j<rows; j++) {
          tempField[i][j].mult(0.992);
        }
      }
  }
  void onMouseDrag() {
    PVector direc = new PVector(mouseX-pmouseX, mouseY-pmouseY).normalize();
    drawField(pmouseX, pmouseY, direc.mult(force));
  }

  void saveField() {
    try {
      FileWriter out = new FileWriter(file);
      for (int i=0; i<cols; i++) {
        for (int j=0; j<rows; j++) {
          out.write(field[i][j].x+","+field[i][j].y+"\t");
        }
        out.write("\r\n");
      }
      out.close();
    }
    catch(Exception e) {
    }
  }

  void readField() throws IOException {
    try {
      BufferedReader in = new BufferedReader(new FileReader(file));
      String line;
      for (int i = 0; (line = in.readLine()) != null; i++) {
        String[] temp = line.split("\t"); 
        for (int j=0; j<temp.length; j++) {
          String[] xy = temp[j].split(",");
          float x = Float.parseFloat(xy[0]);
          float y = Float.parseFloat(xy[1]);
          field[i][j] = new PVector(x, y);
        }
      }
      in.close();
    }
    catch(Exception e) {
      throw new IOException("no field.txt");
    }
  }
}


import controlP5.*;

class GUI {
  ControlP5 cp5;
  Slider sliderR;
  Slider sliderF;
  Slider sliderS;
  GUI(PApplet thePApplet){
    cp5 = new ControlP5(thePApplet);
  }
  
  void setup(){
    cp5.setColorBackground(0x141414);
    sliderR = cp5.addSlider("Radius")
                 .setPosition(980,890)
                 .setRange(1,20)
                 .setValue(12).setSize(150,25);
    sliderF = cp5.addSlider("Force")
                 .setPosition(980,918)
                 .setRange(0.1,0.5)
                 .setValue(0.3).setSize(150,25);
    sliderS = cp5.addSlider("Particle Size")
                 .setPosition(980,862)
                 .setRange(0.8,2)
                 .setValue(1.5).setSize(150,25);
    
  }
  
  int getR(){
    return int(sliderR.getValue());
  }
  
  float getF(){
    return sliderF.getValue();
  }
  
  float getS(){
    return sliderS.getValue();
  }
}

final var STAGE_WIDTH = 1200;
final var STAGE_HEIGHT = 950;
final var NB_PARTICLES = 60000;
final let MAX_PARTICLE_SPEED = 5;

final var MIN_LIFE_TIME = 20;
final var MAX_LIFE_TIME = 80;
final let IMAGE_PATH = "starrynight.jpg";

myVector tabParticles[];
let particleSize = 1.2;
PImage myImage;
var imageW;
var imageH;
color myPixels[];
FlowField ff;
GUI gui;

function setup()
{
 var canvas = createCanvas(1200, 950, P3D);
    canvas.parent('canvasForHTML');
  background(0);
  initializeImage();
  initializeParticles();
  ff = new FlowField(5);
  gui = new GUI(this);
  gui.setup();
}


function preload() {  img = loadImage('data/starrynight.jpg');
}


function initializeImage()
{  imageW = myImage.width;
  imageH = myImage.height;
  myPixels = new color[imageW * imageH];
  myImage.loadPixels();
  myPixels = myImage.pixels;
  image(myImage, 0, 0);
}

function setParticle(var i) {
  tabParticles[i] = new myVector((var)random(imageW), (var)random(imageH));
  tabParticles[i].prevX = tabParticles[i].x;
  tabParticles[i].prevY = tabParticles[i].y;
  tabParticles[i].count = (var)random(MIN_LIFE_TIME, MAX_LIFE_TIME);
  tabParticles[i].myColor = myPixels[(var)(tabParticles[i].y)*imageW + (var)(tabParticles[i].x)];
}

function initializeParticles()
{
  tabParticles = new myVector[NB_PARTICLES];
  for (var i = 0; i < NB_PARTICLES; i++)
  {
    setParticle(i);
  }
}

function draw()
{
  ff.setRadius(gui.getR());
  ff.setForce(gui.getF());
  particleSize = gui.getS();
  let vx;
  let vy;
  PVector v;
  for (var i = 0; i < NB_PARTICLES; i++)
  {
    tabParticles[i].prevX = tabParticles[i].x;
    tabParticles[i].prevY = tabParticles[i].y;
    v = ff.lookup(tabParticles[i].x, tabParticles[i].y);
    vx = v.x;
    vy = v.y;
    vx = constrain(vx, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
    vy = constrain(vy, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
    tabParticles[i].x += vx;
    tabParticles[i].y += vy;
    tabParticles[i].count--;
    if ((tabParticles[i].x < 0) || (tabParticles[i].x > imageW-1) ||
      (tabParticles[i].y < 0) || (tabParticles[i].y > imageH-1) ||
      tabParticles[i].count < 0) {
      setParticle(i);
    }
    strokeWeight(1.5*particleSize);
    stroke(tabParticles[i].myColor, 250);
    line(tabParticles[i].prevX, tabParticles[i].prevY, tabParticles[i].x, tabParticles[i].y);
  }
  ff.updateField();
}

function mouseDragged() {
  if(mouseX>950 && mouseY>830) return;
  ff.onMouseDrag();
}

function keyPressed() {
  //if (key =='s' || key == 'S') {
  //  ff.saveField();
  //}
}

class myVector extends PVector
{
  myVector (let p_x, let p_y) {
    super(p_x, p_y);
  }
  let prevX;
  let prevY;
  var count;
  color myColor;
}



import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

class FlowField {
  PVector[][] field;
  PVector[][] tempField;
  var cols, rows;
  var resolution;
  var affectRadius;
  let force;
  File file = new File(dataPath("field.txt"));

  FlowField(var r) {
    resolution = r;
    cols = 1200 / resolution;
    rows = 950 / resolution;
    field = new PVector[cols][rows];
    tempField = new PVector[cols][rows];
    init();
    affectRadius = 3;
    force = 1;
  }

  function setRadius(var r) {
    affectRadius = r;
  }

  function setForce(let f) {
    force = f;
  }

  function init() {
    try { 
      for (var i=0; i<cols; i++) {
        for (var j=0; j<rows; j++) {
          tempField[i][j] = new PVector(0, 0);
        }
      }
      readField();
    }
    catch(Exception e) {
      for (var i=0; i<cols; i++) {
        for (var j=0; j<rows; j++) {
          field[i][j] = new PVector(0, 0);
        }
      }
    }
  }

  PVector lookup(let x, let y) {
    var column = var(constrain(x/resolution, 0, cols-1));
    var row = var(constrain(y/resolution, 0, rows-1));
    return PVector.add(field[column][row],tempField[column][row]);
  }

  function drawBrush() {
    pushStyle();
    noFill();
    stroke(255, 255, 255);
    ellipse(mouseX, mouseY, affectRadius*10, affectRadius*10);
    popStyle();
  }

  function drawField(let x, let y, PVector v) {
    var column = var(constrain(x/resolution, 0, cols-1));
    var row = var(constrain(y/resolution, 0, rows-1));
    for (var i=-affectRadius; i<=affectRadius; i++) {
      for (var j=-affectRadius; j<=affectRadius; j++) {
        if (i*i+j*j<affectRadius*affectRadius) {
          try { 
            tempField[column+i][row+j].add(v).mult(0.9);
          }
          catch(Exception e) {
          }
        }
      }
    }
  }
  
  function updateField(){
    for (var i=0; i<cols; i++) {
        for (var j=0; j<rows; j++) {
          tempField[i][j].mult(0.992);
        }
      }
  }
  function onMouseDrag() {
    PVector direc = new PVector(mouseX-pmouseX, mouseY-pmouseY).normalize();
    drawField(pmouseX, pmouseY, direc.mult(force));
  }

  function saveField() {
    try {
      FileWriter out = new FileWriter(file);
      for (var i=0; i<cols; i++) {
        for (var j=0; j<rows; j++) {
          out.write(field[i][j].x+","+field[i][j].y+"\t");
        }
        out.write("\r\n");
      }
      out.close();
    }
    catch(Exception e) {
    }
  }

  function readField() throws IOException {
    try {
      BufferedReader in = new BufferedReader(new FileReader(file));
      let line;
      for (var i = 0; (line = in.readLine()) != null; i++) {
        let[] temp = line.split("\t"); 
        for (var j=0; j<temp.length; j++) {
          let[] xy = temp[j].split(",");
          let x = let.parselet(xy[0]);
          let y = let.parselet(xy[1]);
          field[i][j] = new PVector(x, y);
        }
      }
      in.close();
    }
    catch(Exception e) {
      throw new IOException("no field.txt");
    }
  }
}


import controlP5.*;

class GUI {
  ControlP5 cp5;
  Slider sliderR;
  Slider sliderF;
  Slider sliderS;
  GUI(PApplet thePApplet){
    cp5 = new ControlP5(thePApplet);
  }
  
  function setup(){
    cp5.setColorBackground(0x141414);
    sliderR = cp5.addSlider("Radius")
                 .setPosition(980,890)
                 .setRange(1,20)
                 .setValue(12).setSize(150,25);
    sliderF = cp5.addSlider("Force")
                 .setPosition(980,918)
                 .setRange(0.1,0.5)
                 .setValue(0.3).setSize(150,25);
    sliderS = cp5.addSlider("Particle Size")
                 .setPosition(980,862)
                 .setRange(0.8,2)
                 .setValue(1.5).setSize(150,25);
    
  }
  
  var getR(){
    return var(sliderR.getValue());
  }
  
  let getF(){
    return sliderF.getValue();
  }
  
  let getS(){
    return sliderS.getValue();
  }
}

I felt like doing nothing this evening, so here you go:

Also, I recommend getting the "Better Comments" extension/plugin for your code editor of choice , since I used those here a lot

const STAGE_WIDTH = 1200;
const STAGE_HEIGHT = 950;
const NB_PARTICLES = 60000;
const MAX_PARTICLE_SPEED = 5;

const MIN_LIFE_TIME = 20;
const MAX_LIFE_TIME = 80;
const IMAGE_PATH = "starrynight.jpg";

let tabParticles = [];
let particleSize = 1.2;
let myImage;
let imageW;
let imageH;
let myPixels = [];
let ff;
let gui;

function setup()
{
  size(1200, 950, WEBGL);
  background(0);
  initializeImage();
  initializeParticles();
  ff = new FlowField(5);
  // ! CHANGE THIS AND ALL OF THE OTHER GUI STUFF THAT COME AFTER IT!
  // gui = new GUI(this);
  // gui.setup();
}

function initializeImage()
{
  myImage = loadImage(IMAGE_PATH);
  imageW = myImage.width;
  imageH = myImage.height;
  myPixels = new color[imageW * imageH]; // ? dunno
//   myImage.loadPixels();
//   myPixels = myImage.pixels;
  image(myImage, 0, 0);
}

function setParticle(i) {
  tabParticles[i] = new myVector(random(imageW), random(imageH));
  tabParticles[i].prevX = tabParticles[i].x;
  tabParticles[i].prevY = tabParticles[i].y;
  tabParticles[i].count = random(MIN_LIFE_TIME, MAX_LIFE_TIME);
  tabParticles[i].myColor = myPixels[(int)(tabParticles[i].y)*imageW + (int)(tabParticles[i].x)];
}

function initializeParticles()
{
//   tabParticles = new myVector[NB_PARTICLES];
  for(let i = 0; i < NB_PARTICLES; i ++)
    tabParticles.push(new myVector())

  for (let i = 0; i < NB_PARTICLES; i++)
  {
    setParticle(i);
  }
}

function draw()
{
  ff.setRadius(gui.getR());
  ff.setForce(gui.getF());
  particleSize = gui.getS();
  let vx;
  let vy;
  let v;
  for (let i = 0; i < NB_PARTICLES; i++)
  {
    tabParticles[i].prevX = tabParticles[i].x;
    tabParticles[i].prevY = tabParticles[i].y;
    v = ff.lookup(tabParticles[i].x, tabParticles[i].y);
    vx = v.x;
    vy = v.y;
    vx = constrain(vx, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
    vy = constrain(vy, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
    tabParticles[i].x += vx;
    tabParticles[i].y += vy;
    tabParticles[i].count--;
    if ((tabParticles[i].x < 0) || (tabParticles[i].x > imageW-1) ||
      (tabParticles[i].y < 0) || (tabParticles[i].y > imageH-1) ||
      tabParticles[i].count < 0) {
      setParticle(i);
    }
    strokeWeight(1.5*particleSize);
    stroke(tabParticles[i].myColor, 250);
    line(tabParticles[i].prevX, tabParticles[i].prevY, tabParticles[i].x, tabParticles[i].y);
  }
  ff.updateField();
}

function mouseDragged() {
  if(mouseX>950 && mouseY>830) return;
  ff.onMouseDrag();
}

function keyPressed() {
  //if (key =='s' || key == 'S') {
  //  ff.saveField();
  //}
}

class myVector extends PVector
{
  constructor (p_x, p_y) {
    super(p_x, p_y);
    this.prevX
    this.prevY
    this.count
    this.myColor
  }
}

class FlowField {


  constructor(r) {

    this.field; // PVector[][]
    this.tempField; // PVector[][]
    this.cols; this.rows; // int
    this.resolution; // int
    this.affectRadius; // int
    this.force; // float
    this.file = new File(dataPath("field.txt")); // File
    // ! You'll need to have a preload loadStrings(filepath) or fetch(filename).then(blob => blob.text()).then(text => ... )
    // ! can't be bothered to do this right now
    throw "Ult1:   change this!!!    also delete this line  (just incase: ~129th line)"


    resolution = r;
    cols = 1200 / resolution;
    rows = 950 / resolution;
    field = new PVector[cols][rows];
    tempField = new PVector[cols][rows];
    init();
    affectRadius = 3;
    force = 1;
  }

  setRadius(r) {
    affectRadius = r;
  }

  setForce(f) { 
    force = f;
  }

  // FROM HERE ON I CONVERTED IT WHILE HALF ASLEEP (11pm)

  init() {
    try { 
      for (let i=0; i<cols; i++) {
        for (let j=0; j<rows; j++) {
          tempField[i][j] = createVector()
        }
      }
      readField();
    }
    catch(e) {
      for (let i=0; i<cols; i++) {
        for (let j=0; j<rows; j++) {
          field[i][j] = createVector()
        }
      }
    }
  }

  lookup(x, y) {
    let column = int(constrain(x/resolution, 0, cols-1));
    let row = int(constrain(y/resolution, 0, rows-1));
    return p5.Vector.add(field[column][row],tempField[column][row]);
  }

  drawBrush() {
    pushStyle();
    noFill();
    stroke(255, 255, 255);
    ellipse(mouseX, mouseY, affectRadius*10, affectRadius*10);
    popStyle();
  }

  drawField(x, y, v) {
    let column = int(constrain(x/resolution, 0, cols-1));
    let row = int(constrain(y/resolution, 0, rows-1));
    for (let i=-affectRadius; i<=affectRadius; i++) {
      for (let j=-affectRadius; j<=affectRadius; j++) {
        if (i*i+j*j<affectRadius*affectRadius) {
          try { 
            tempField[column+i][row+j].add(v).mult(0.9);
          }
          catch(e) {
          }
        }
      }
    }
  }
  
  updateField(){
    for (let i=0; i<cols; i++) {
        for (let j=0; j<rows; j++) {
          tempField[i][j].mult(0.992);
        }
      }
  }
  onMouseDrag() {
    let direc = createVector(mouseX-pmouseX, mouseY-pmouseY).normalize()
    drawField(pmouseX, pmouseY, direc.mult(force));
  }

  saveField() {
    try {
      // FileWriter out = new FileWriter(file);
      throw "FileWriter doesn't exist in Javascript, line: 215"
      // ! this doesn't exist! in javascript, I don't remember how to do this, but you can just search it on Google
      for (let i=0; i<cols; i++) {
        for (let j=0; j<rows; j++) {
          out.write(field[i][j].x+","+field[i][j].y+"\t");
        }
        out.write("\r\n");
      }
      out.close();
    }
    catch(e) {
    }
  }

  readField() {
    throw "once again, BufferedReader is just not a thing, but it's for file reading, so you can just loadString(\"file\") or fetch... line: 230"
    try {
      let _in = new BufferedReader(new FileReader(file));
      let line;
      for (let i = 0; (line = _in.readLine()) != null; i++) {
        let temp = line.split("\t"); 
        for (let j=0; j<temp.length; j++) {
          let xy = temp[j].split(",");
          let x = Float.parseFloat(xy[0]);
          let y = Float.parseFloat(xy[1]);
          field[i][j] = createVector(x, y);
        }
      }
      _in.close();
    }
    catch(e) {
      throw new IOException("no field.txt");
    }
  }
}
// I think you should just do this with html to be honest
// copy this into your body (below the <script src="sketch.js"></script>, or above it):

/*

<div id="GUI">
  <label for="Radius">Radius</label>
  <input type="range" name="Radius" id="slider-radius" min="1" max="20" step="0.5" value="10">
  <br>
  <label for="Force">Force</label>
  <input type="range" name="Force" id="slider-force" min="0.1" max="0.5" step="0.01" value="0.3">
  <br>
  <label for="Particle-size">Particle size</label>
  <input type="range" name="Particle-size" id="slider-particle-size" min="0.8" max="2" step="0.05" value="1.5">
</div>
      
<style>
  html, body {
    background-color: #141414;
  }

  label {
    color: #ccc;
  }
</style>


*/

// color: #ccc    is the same as    text-color = #CCCCCC      in some other language, maybe...





class GUI {

  constructor(){
    throw "don't declare the GUI class, instead do GUI.getR(), GUI.getF() ...    line: 288, you should be able to see line # in your console"
  }

  static getR(){
    return document.getElementById("slider-radius").value;
  }
  
  static getF(){
    return document.getElementById("slider-force").value
  }
  
  static getS(){
    return document.getElementById("slider-particle-size").value
  }
}

// static = same between ALL the classes(instances) of a class thing, so:
// class A { static x = 7; this.y = 5 } 
// * console.log(A.x) -> 7
// ! console.log(A.y) -> probably null, since you need to do console.log(new A().y) 

// here I have it to keep the GUI class, but there is no need to keep this stuff

You will still need to do A LOT of work to get this going, like translating classes, for example class myVector extends PVector {...} . In p5.js there is no PVector, but I'm pretty sure there's p5.Vector though, so class myVector extends p5.Vector might work. File reading and writing will also be messed up, since JavaScript doesn't have the whole new File() and all the stuff like that.

You will also need to deal with CORS, good luck! I also changed most of the GUI stuff to .html stuff and a static class, bit hard words there, but can't really change them to anything.

Anyways, I probably made mistakes here!

It's 11:40 pm here by now, so yeah! this is very more or less transferrrr

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM