Related
On macOS Big Sur, running JDK 17 and JavaFX 18, when you have an irregular clipping region, the clipped image ends up badly pixelated when printed.
The pixelation happens both on actual paper (on my Epson printer), as well as when saved to a PDF (using macOSs innate "Save to PDF" feature).
If you have a clip region with a Rectangle, there is no pixelation, everything looks smooth and crisp.
Note, that it must be a Rectangle, a rectangular Polygon has problems as well.
This is a screen shot of the actual screen rendering:
Next is a screen shot of viewing the resulting PDF.
You can see both the text and lines are pixelated within the circle.
Seems to me that its rendering the node in to the "PDF resolution", which is 72 DPI (not actual printer resolution), and then masking the region using a bitmap operation rather than actually clipping the content to the irregular border. But it special cases the Rectangle and clips to that.
Is there anything that can be done about this?
Below is a sample demonstrating this.
package pkg;
import javafx.application.Application;
import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class App extends Application {
Pane printPane;
#Override
public void start(Stage stage) {
var scene = new Scene(getView(), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private Pane getView() {
var flowPane = new FlowPane(getCirclePane(), getRectPane());
var b = new Button("Print");
printPane = flowPane;
b.setOnAction((t) -> {
var job = PrinterJob.createPrinterJob();
if (job != null && job.showPrintDialog(null)) {
if (job.printPage(printPane)) {
job.endJob();
}
}
});
var bp = new BorderPane();
bp.setCenter(flowPane);
bp.setBottom(b);
return bp;
}
private Pane getCirclePane() {
var pane = new Pane();
var circle = new Circle(100, 100, 45);
pane.setClip(circle);
var text = new Text(75, 100, "Circle");
pane.getChildren().add(text);
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
return pane;
}
private Pane getRectPane() {
var pane = new Pane();
var rect = new Rectangle(50, 50, 200, 125);
pane.setClip(rect);
var text = new Text(125, 100, "Rectangle");
pane.getChildren().add(text);
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
return pane;
}
}
Update:
I came up with this as a workaround.
Simply, that which I can't clip, I can paint over.
I create a new rect the size of the overall scene, then use Shape.subtract(...) to punch a hole in it in the shape of my clip region. I fill this new shape with my background color. Then I put this in a new pane, and use a Stack pane to layer it on top of my master. It's imperfect, and a little bit fiddly, and it chafes having to do this. But it works and is easy to explain.
So, using the circle as an example:
private Pane getCirclePane() {
var pane = new Pane();
var circle = new Circle(100, 100, 45);
var text = new Text(75, 100, "Circle");
pane.getChildren().add(text);
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
Rectangle rect = new Rectangle(0, 0, 250, 200);
Shape overlay = Shape.subtract(rect, circle);
overlay.setFill(Color.WHITE);
Pane overlayPane = new Pane();
overlayPane.getChildren().add(overlay);
pane = new StackPane(pane, overlayPane);
return pane;
}
As an interim workaround, you may be able to adapt the approach seen here; it uses Shape.subtract() to create a suitable matte that functions like the corresponding clip.
var c = new Circle(100, 100, 80);
var r = new Rectangle(250, 200);
Shape matte = Shape.subtract(r, c);
matte.setFill(Color.WHITE);
pane.getChildren().add(matte);
As printed:
As tested:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage stage) {
var scene = new Scene(getView());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private Pane getView() {
final var printBox = new HBox(8, getCirclePane(), getRectPane());
var b = new Button("Print");
b.setOnAction((t) -> {
var job = PrinterJob.createPrinterJob();
if (job != null && job.showPrintDialog(null)) {
if (job.printPage(printBox)) {
job.endJob();
}
}
});
var bp = new BorderPane();
bp.setCenter(printBox);
bp.setBottom(b);
BorderPane.setAlignment(b, Pos.BOTTOM_CENTER);
return bp;
}
private Pane getCirclePane() {
var pane = new Pane();
var text = new Text(80, 100, "Circle");
pane.getChildren().add(text);
addPattern(pane);
var c = new Circle(100, 100, 80);
var r = new Rectangle(250, 200);
Shape matte = Shape.subtract(r, c);
matte.setFill(Color.WHITE);
pane.getChildren().add(matte);
return pane;
}
private Pane getRectPane() {
var pane = new Pane();
var rect = new Rectangle(50, 50, 200, 125);
pane.setClip(rect);
var text = new Text(125, 100, "Rectangle");
pane.getChildren().add(text);
addPattern(pane);
return pane;
}
private void addPattern(Pane pane) {
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
}
}
I have several buttons that I created like this:
In the setup()
PImage[] imgs1 = {loadImage("AREA1_1.png"),loadImage("AREA1_2.png"),loadImage("AREA1_3.png")};
cp5.addButton("AREA_1") // The button
.setValue(128)
.setPosition(-60,7) // x and y relative to the group
.setImages(imgs1)
.updateSize()
.moveTo(AreaRingGroup); // add it to the group
;
After draw()
void AREA_1(){
println("AREA_1");
if (port != null){
port.write("a\n");
}
Now, I have to add about 24 small buttons from images that are going to be seen as 1 circle. But instead of adding them using cp5.addButton like above, I thought I should create a class.
I want to extend the existing CP5 button class because I want to be able to reach the cp5 parameters; like using .setImages() for 3 mouseClick conditions in the button images.
My problem is, I couldn't figure out
How to extend the CP5 button class, so that I can use angles (since I want to place buttons in a circular way) instead of giving x,y positions.
How to use serial port writings using this class, like the above void (AREA_1) example.
Here is my silly attempt:
class myButton extends Button
{
myButton(ControlP5 cp5, String theName)
{
super(cp5, cp5.getTab("default"), theName, 0,0,0, autoWidth, autoHeight);
}
PVector pos = new PVector (0,0);
PVector center = new PVector(500,500);
PImage[] img = new PImage[3];
String lable;
int sizeX = 10, sizeY=10;
color imgColor;
Boolean pressed = false;
Boolean clicked = false;
//Constructor
myButton(float a, String theName)
{
//Angle between the center I determined and the position of the
button.
a = degrees (PVector.angleBetween(center, pos));
//Every button will have 3 images for the 3 mouseClick conditions.
for (int i = 0; i < 2; ++i)
(img[i] = loadImage(lable + i + ".png")).resize(sizeX, sizeY);
}
}
Laancelot's idea isn't bad, however, due to controlP5's OOP architecture it might not be possible, or at least trivial, to extend the controlP5's Button class to achieve your goal from a controlP5 sketch.
I'm making the assumption this is connected to other LED ring related questions you posted around this time (such as this one).
It might be wrong (and I'd be interested in seeing a solution), but without forking/modifying the controlP5 library itself, the inheritance chain gets a bit tricky.
Ideally you'd just need to extend Button and be done with it, however the method used to tell states (if a button over/pressed/etc.) is the Controller superclass's inside() method. To work with your image you'd need override this method to and swap the logic so it takes into account the alpha transparency of your png button skin (e.g. if the pixel under the cursor is opaque then it's inside otherwise it's not)
Here's a rough illustration you were constrained to using a Processing sketch:
public abstract class CustomController< T > extends Controller< T> {
public CustomController( ControlP5 theControlP5 , String theName ) {
super( theControlP5 , theControlP5.getDefaultTab( ) , theName , 0 , 0 , autoWidth , autoHeight );
theControlP5.register( theControlP5.papplet , theName , this );
}
protected CustomController( final ControlP5 theControlP5 , final ControllerGroup< ? > theParent , final String theName , final float theX , final float theY , final int theWidth , final int theHeight ) {
super(theControlP5, theParent, theName, theX, theY, theWidth, theHeight);
}
boolean inside( ) {
/* constrain the bounds of the controller to the dimensions of the cp5 area, required since PGraphics as render
* area has been introduced. */
//float x0 = PApplet.max( 0 , x( position ) + x( _myParent.getAbsolutePosition( ) ) );
//float x1 = PApplet.min( cp5.pgw , x( position ) + x( _myParent.getAbsolutePosition( ) ) + getWidth( ) );
//float y0 = PApplet.max( 0 , y( position ) + y( _myParent.getAbsolutePosition( ) ) );
//float y1 = PApplet.min( cp5.pgh , y( position ) + y( _myParent.getAbsolutePosition( ) ) + getHeight( ) );
//return ( _myControlWindow.mouseX > x0 && _myControlWindow.mouseX < x1 && _myControlWindow.mouseY > y0 && _myControlWindow.mouseY < y1 );
// FIXME: add alpha pixel logic here
return true;
}
}
public class CustomButton<Button> extends CustomController< Button > {
protected CustomButton( ControlP5 theControlP5 , ControllerGroup< ? > theParent , String theName , float theDefaultValue , int theX , int theY , int theWidth , int theHeight ) {
super( theControlP5 , theParent , theName , theX , theY , theWidth , theHeight );
_myValue = theDefaultValue;
_myCaptionLabel.align( CENTER , CENTER );
}
// isn't this fun ?
//public CustomButton setImage( PImage theImage ) {
// return super.setImage( theImage , DEFAULT );
//}
}
I would suggest a simpler workaround: simply add smaller buttons in a ring layout (using polar to cartesian coordinate conversion):
import controlP5.*;
ControlP5 cp5;
void setup(){
size(600, 600);
background(0);
cp5 = new ControlP5(this);
int numLEDs = 24;
float radius = 240;
for(int i = 0; i < numLEDs; i++){
float angle = (TWO_PI / numLEDs * i) - HALF_PI;
cp5.addButton(nf(i, 2))
.setPosition(width * 0.5 + cos(angle) * radius, height * 0.5 + sin(angle) * radius)
.setSize(30, 30);
}
}
void draw(){}
Sure, not as pretty, but much much simpler.
Hopefully rendering the ring image drawn behind as a background and changing the button colour might be enough to get the point across.
It might be simpler to skip ControlP5 altogether and make your own button class for such a custom behaviour.
Let's say you want to have a square button in a ring layout that also rotates along the ring. This rotation will make checking the bounds tricker, but not impossible. You could hold track of the button's 2D transformation matrix using PMatrix2D) to render the button, but use the inverse of this transformation matrix to convert between Processing's global coordinate system to the button's local coordinate system. This would make checking if the button hovered trivial again (as the mouse converted to local coordinates would be within the button's bounding box). Here's an example:
TButton btn;
void setup(){
size(600, 600);
btn = new TButton(0, 300, 300, 90, 90, radians(45));
}
void draw(){
background(0);
btn.update(mouseX, mouseY, mousePressed);
btn.draw();
}
class TButton{
PMatrix2D transform = new PMatrix2D();
PMatrix2D inverseTransform;
int index;
int x, y, w, h;
float angle;
boolean isOver;
boolean isPressed;
PVector cursorGlobal = new PVector();
PVector cursorLocal = new PVector();
color fillOver = color(192);
color fillOut = color(255);
color fillOn = color(127);
TButton(int index, int x, int y, int w, int h, float angle){
this.index = index;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.angle = angle;
transform.translate(x, y);
transform.rotate(angle);
inverseTransform = transform.get();
inverseTransform.invert();
}
void update(int mx, int my, boolean mPressed){
cursorGlobal.set(mx, my);
inverseTransform.mult(cursorGlobal, cursorLocal);
isOver = ((cursorLocal.x >= 0 && cursorLocal.x <= w) &&
(cursorLocal.y >= 0 && cursorLocal.y <= h));
isPressed = mPressed && isOver;
}
void draw(){
pushMatrix();
applyMatrix(transform);
pushStyle();
if(isOver) fill(fillOver);
if(isPressed) fill(fillOn);
rect(0, 0, w, h);
popStyle();
popMatrix();
}
}
If you can draw one button, you can draw many buttons, in a ring layout:
int numLEDs = 24;
TButton[] ring = new TButton[numLEDs];
TButton selectedLED;
void setup(){
size(600, 600);
float radius = 240;
float ledSize= 30;
float offsetX = width * 0.5;
float offsetY = height * 0.5;
for(int i = 0 ; i < numLEDs; i++){
float angle = (TWO_PI / numLEDs * i) - HALF_PI;
float x = offsetX + (cos(angle) * radius);
float y = offsetY + (sin(angle) * radius);
ring[i] = new TButton(i, x, y, ledSize, ledSize, angle);
}
println("click to select");
println("click and drag to change colour");
}
void draw(){
background(0);
for(int i = 0 ; i < numLEDs; i++){
ring[i].update(mouseX, mouseY);
ring[i].draw();
}
}
void onButtonClicked(TButton button){
selectedLED = button;
println("selected", selectedLED);
}
void mouseReleased(){
for(int i = 0 ; i < numLEDs; i++){
ring[i].mouseReleased();
}
}
void mouseDragged(){
if(selectedLED != null){
float r = map(mouseX, 0, width, 0, 255);
float g = map(mouseY, 0, height, 0, 255);
float b = 255 - r;
selectedLED.fillColor = color(r, g, b);
}
}
class TButton{
PMatrix2D transform = new PMatrix2D();
PMatrix2D inverseTransform;
int index;
float x, y, w, h;
float angle;
boolean isOver;
PVector cursorGlobal = new PVector();
PVector cursorLocal = new PVector();
color fillColor = color(255);
TButton(int index, float x, float y, float w, float h, float angle){
this.index = index;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.angle = angle;
transform.translate(x, y);
transform.rotate(angle);
inverseTransform = transform.get();
inverseTransform.invert();
}
void update(int mx, int my){
cursorGlobal.set(mx, my);
inverseTransform.mult(cursorGlobal, cursorLocal);
isOver = ((cursorLocal.x >= 0 && cursorLocal.x <= w) &&
(cursorLocal.y >= 0 && cursorLocal.y <= h));
}
void mouseReleased(){
if(isOver) onButtonClicked(this);
}
void draw(){
pushMatrix();
applyMatrix(transform);
pushStyle();
if(isOver) {
fill(red(fillColor) * .5, green(fillColor) * .5, blue(fillColor) * .5);
}else{
fill(fillColor);
}
rect(0, 0, w, h);
popStyle();
popMatrix();
}
String toString(){
return "[TButton index=" + index + "]";
}
}
This isn't bad, but it easily gets complicated, right ?
What is you apply a global transformation in draw(): that may need to be handled by the button ? What if the button is nested in a bunch of pushMatrix()/popMatrix() calls when .draw() is called ? etc...
Could this be simpler ? Sure, the LEDs appear as rotated squares on a ring layout, however the important part that lights up is circular in shape. A rotated circle looks the same regardless of the angle. Checking if the mouse is over is trivial, as you can see in the Processing RollOver example : simply check if the distance between a circle's position and the cursor is smaller than the circle's radius.
Here's an example using circular buttons (removing the need for handling coordinate space transformations):
int numLEDs = 24;
CButton[] ring = new CButton[numLEDs];
CButton selectedLED;
void setup(){
size(600, 600);
float radius = 240;
float ledSize= 30;
float offsetX = width * 0.5;
float offsetY = height * 0.5;
for(int i = 0 ; i < numLEDs; i++){
float angle = (TWO_PI / numLEDs * i) - HALF_PI;
float x = offsetX + (cos(angle) * radius);
float y = offsetY + (sin(angle) * radius);
ring[i] = new CButton(i, x, y, ledSize);
}
println("click to select");
println("click and drag to change colour");
}
void draw(){
background(0);
for(int i = 0 ; i < numLEDs; i++){
ring[i].update(mouseX, mouseY);
ring[i].draw();
}
}
void onButtonClicked(CButton button){
selectedLED = button;
println("selected", selectedLED);
}
void mouseReleased(){
for(int i = 0 ; i < numLEDs; i++){
ring[i].mouseReleased();
}
}
void mouseDragged(){
if(selectedLED != null){
float r = map(mouseX, 0, width, 0, 255);
float g = map(mouseY, 0, height, 0, 255);
float b = 255 - r;
selectedLED.fillColor = color(r, g, b);
}
}
class CButton{
int index;
float x, y, radius, diameter;
boolean isOver;
color fillColor = color(255);
CButton(int index, float x, float y, float radius){
this.index = index;
this.x = x;
this.y = y;
this.radius= radius;
diameter = radius * 2;
}
void update(int mx, int my){
isOver = dist(x, y, mx, my) < radius;
}
void mouseReleased(){
if(isOver) onButtonClicked(this);
}
void draw(){
pushMatrix();
pushStyle();
if(isOver) {
fill(red(fillColor) * .5, green(fillColor) * .5, blue(fillColor) * .5);
}else{
fill(fillColor);
}
ellipse(x, y, diameter, diameter);
popStyle();
popMatrix();
}
String toString(){
return "[CButton index=" + index + "]";
}
}
Using these circular buttons on top of your NeoPixel ring image and perhaps with a bit of glow might look pretty good and simpler to achieve in a Processing sketch than taking the ControlP5 route. Don't get me wrong, it's a great library, but for custom behaviours it sometimes makes more sense to use your own custom code.
One other aspect to think is the overall interaction.
If this interface would be used to set the colors of an LED ring once, then it might ok, although it would take numLEDs * 3 interactions to set all the LEDs with a custom colour. If this was for a custom frame by frame LED animation tool then the interaction would get exhausting after a few frames.
My problem is as in the title. I am trying to write a simple game in processing with a car that you can drive on a 2D plane. I wanted to create a rotation of the car since it seems crucial so I did it as described here:Rotating points in 2D
But my implementation seems to fail a bit. You see, when I hit left of right arrow the car actually rotates but shrinks in size as it is rotating and after few turns it completely dissapears. Can you show me what am I missing here? Thanks in advance! Code of my functions:
class Point
{
float x, y;
Point(float xx, float yy)
{
x = xx;
y = yy;
}
Point()
{
x = y = 0.0;
}
void Rotate(Point center, float angle)
{
float s = sin(angle);
float c = cos(angle);
y = center.y + ((y-center.y) * c + (x-center.x) * s);
x = center.x + ((x-center.x) * c - (y-center.y) * s);
}
}
class Car
{
Point LT;
Point RT;
Point LB;
Point RB;
Point center;
float r;
float acceleration;
Car()
{
LT = new Point(10, 10);
RT = new Point (30, 10);
LB = new Point(10, 50);
RB = new Point(30, 50);
r = sqrt(pow(15-30, 2) + pow(25-10, 2));
}
Car(Point lt, Point rt, Point lb, Point rb)
{
LT = lt;
RT = rt;
LB = lb;
RB = rb;
center = new Point(abs((LT.x - RT.x)/2), abs((LT.y - LB.y)/2));
r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
}
Car(Point Center, float w, float h)
{
center = Center;
LT = new Point(center.x - w/2, center.y - h/2);
RT = new Point (center.x + w/2, center.y - h/2);
LB = new Point(center.x - w/2, center.y + h/2);
RB = new Point(center.x + w/2, center.y + h/2);
r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
}
void Show()
{
fill(45, 128, 156);
beginShape();
vertex(LT.x, LT.y);
vertex(RT.x, RT.y);
vertex(RB.x, RB.y);
vertex(LB.x, LB.y);
endShape();
}
void Update()
{
}
void Turn(float angle)
{
LT.Rotate(center, angle);
RT.Rotate(center, angle);
RB.Rotate(center, angle);
LB.Rotate(center, angle);
}
void Accelerate(float accel)
{
}
}
In main I only use car.Show() and I turn by -0.1 per left cliock and 0.1 per right click
EDIT
If you want to see whole code visit my github repo
Unfortunately I can't explain more at the moment, but here's a simpler option using one of the formulas you've pointed to:
Car car = new Car();
void setup(){
size(300,300);
// this helps draw rectangles from centre (as opposed to corner (default))
rectMode(CENTER);
car.position.set(150,150);
}
void draw(){
background(255);
if(keyPressed){
if(keyCode == UP){
car.speed = 1;
}
}
car.draw();
}
void keyPressed(){
if(keyCode == LEFT){
car.steer -= radians(10);
}
if(keyCode == RIGHT){
car.steer += radians(10);
}
}
void keyReleased(){
if(keyCode == UP){
car.speed = 0;
}
}
class Car{
PVector position = new PVector();
PVector velocity = new PVector();
float speed;
float steer;
void update(){
// use the same polar to cartesian coordinates formulate for quick'n'dirty steering
velocity.set(cos(steer) * speed,sin(steer) * speed);
// update position based on velocity
position.add(velocity);
}
void draw(){
update();
// use a nested coordinate system to handle translation and rotation for us
// order of operations is important
pushMatrix();
translate(position.x,position.y);
rotate(steer);
rect(0,0,30,15);
popMatrix();
}
}
Update
The main issue with points shrinking is you're cumulatively transforming the points when you rotate them. After each transformation there is no history of what the x,y were. Instead you should return a new point that is transformed, thus "remembering" the old x,y position.
Bellow is a tweaked version of your code, minus the two constructor variants.
Hopefully the comments will help:
Car car = new Car();
void setup(){
size(300,300);
}
void draw(){
if(keyCode == UP){
if(keyPressed){
car.Accelerate(1);
}else{
car.Accelerate(0);
}
}
car.Update();
background(255);
car.Show();
}
void keyPressed(){
if(keyCode == LEFT){
car.Turn(radians(-3));
}
if(keyCode == RIGHT){
car.Turn(radians(+3));
}
}
class Point
{
float x, y;
Point(float xx, float yy)
{
x = xx;
y = yy;
}
Point()
{
x = y = 0.0;
}
Point Rotate(Point center, float angle)
{
float s = sin(angle);
float c = cos(angle);
// return a new point (a rotated copy), rather than overwriting this one
return new Point(center.x + ((x-center.x) * c - (y-center.y) * s),
center.y + ((y-center.y) * c + (x-center.x) * s));
}
// translate by another point
void AddToSelf(Point point){
this.x += point.x;
this.y += point.y;
}
// pretty print info when using println()
String toString(){
return "[Point x=" + x + " y="+ y +"]";
}
}
class Car
{
Point LT;
Point RT;
Point LB;
Point RB;
Point center;
float r;
float acceleration;
// car angle: used to compute velocity and update vertices
float angle;
// car position: used to offset rendering position of the corners
Point position;
// car velocity: amount by which position translates
Point velocity = new Point();
Car()
{
float x = 10;
float y = 10;
float w = 40;
float h = 20;
// setup corners with no translation
LT = new Point(0 , 0 );
RT = new Point(0 + w, 0 );
LB = new Point(0 , 0 + h);
RB = new Point(0 + w, 0 + h);
// setup initial position
position = new Point(x,y);
center = new Point(w / 2, h / 2);
r = sqrt(pow(15-30, 2) + pow(25-10, 2));
}
//Car(Point lt, Point rt, Point lb, Point rb)
//{
// LT = lt;
// RT = rt;
// LB = lb;
// RB = rb;
// center = new Point(abs((LT.x - RT.x)/2), abs((LT.y - LB.y)/2));
// r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
//}
//Car(Point Center, float w, float h)
//{
// center = Center;
// LT = new Point(center.x - w/2, center.y - h/2);
// RT = new Point (center.x + w/2, center.y - h/2);
// LB = new Point(center.x - w/2, center.y + h/2);
// RB = new Point(center.x + w/2, center.y + h/2);
// r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
//}
void Show()
{
fill(45, 128, 156);
beginShape();
// render corners offset by the car position
vertex(position.x + LT.x, position.y + LT.y);
vertex(position.x + RT.x, position.y + RT.y);
vertex(position.x + RB.x, position.y + RB.y);
vertex(position.x + LB.x, position.y + LB.y);
endShape(CLOSE);
}
void Update()
{
// update velocity based on car angle and acceleration
velocity.x = cos(angle) * acceleration;
velocity.y = sin(angle) * acceleration;
// update position based on velocity
position.AddToSelf(velocity);
}
void Turn(float angle)
{
this.angle += angle;
// replace the old point with the transformed points
// (rather than continuosly transforming the same point)
LT = LT.Rotate(center, angle);
RT = RT.Rotate(center, angle);
RB = RB.Rotate(center, angle);
LB = LB.Rotate(center, angle);
}
void Accelerate(float accel)
{
acceleration = accel;
}
}
I did not find a direct function in Color section or since there is no direct C# Unity function for picking the color from post render. How is the best approach for picking the color in the mouse position?
I have done research and looks like there is posible to make a screenshot and then look into the texture calculating the mouse position.
Input.GetMouseButtonDown(0)
Application.CaptureScreenshot("Screenshot.png");
// get the color pixel in the same coordinates of the mouse position
Vector3 mouseCoordinates = Input.mousePosition;
myFinalColor = tex.GetPixel((int)mouseCoordinates.x, (int)mouseCoordinates.y);
Or do I have to make a second camera and attach it to a mesh render?
You just need to use GetPixel(x,y)
This is very simple.
Save Screenshot to Texture2D for example MyTexture
And add .GetPixel( x postion of moue , Y position of mouse )
Save it to your color for GetScreenShot ( make your View to Texture2D )
Color TheColorPicked;
if (Input.GetMouseButtonDown(0))
{
TheColorPicked = MyTexture.GetPixel(Input.mousePosition.x,
Input.mousePosition.y);
}
Yes you can make your own colour picker. Here is the code thanks to Git-hub this page.
using UnityEngine;
using System.Collections;
// relies on: http://forum.unity3d.com/threads/12031-create-random-colors?p=84625&viewfull=1#post84625
public class ColorPicker : MonoBehaviour {
public bool useDefinedPosition = false;
public int positionLeft = 0;
public int positionTop = 0;
// the solid texture which everything is compared against
public Texture2D colorPicker;
// the picker being displayed
private Texture2D displayPicker;
// the color that has been chosen
public Color setColor;
private Color lastSetColor;
public bool useDefinedSize = false;
public int textureWidth = 360;
public int textureHeight = 120;
private float saturationSlider = 0.0F;
private Texture2D saturationTexture;
private Texture2D styleTexture;
public bool showPicker = false;
void Awake() {
if (!useDefinedPosition) {
positionLeft = (Screen.width / 2) - (textureWidth / 2);
positionTop = (Screen.height / 2) - (textureHeight / 2);
}
// if a default color picker texture hasn't been assigned, make one dynamically
if (!colorPicker) {
colorPicker = new Texture2D(textureWidth, textureHeight, TextureFormat.ARGB32, false);
ColorHSV hsvColor;
for (int i = 0; i < textureWidth; i++) {
for (int j = 0; j < textureHeight; j++) {
hsvColor = new ColorHSV((float)i, (1.0f / j) * textureHeight, 1.0f);
colorPicker.SetPixel(i, j, hsvColor.ToColor());
}
}
}
colorPicker.Apply();
displayPicker = colorPicker;
if (!useDefinedSize) {
textureWidth = colorPicker.width;
textureHeight = colorPicker.height;
}
float v = 0.0F;
float diff = 1.0f / textureHeight;
saturationTexture = new Texture2D(20, textureHeight);
for (int i = 0; i < saturationTexture.width; i++) {
for (int j = 0; j < saturationTexture.height; j++) {
saturationTexture.SetPixel(i, j, new Color(v, v, v));
v += diff;
}
v = 0.0F;
}
saturationTexture.Apply();
// small color picker box texture
styleTexture = new Texture2D(1, 1);
styleTexture.SetPixel(0, 0, setColor);
}
void OnGUI(){
if (!showPicker) return;
GUI.Box(new Rect(positionLeft - 3, positionTop - 3, textureWidth + 60, textureHeight + 60), "");
if (GUI.RepeatButton(new Rect(positionLeft, positionTop, textureWidth, textureHeight), displayPicker)) {
int a = (int)Input.mousePosition.x;
int b = Screen.height - (int)Input.mousePosition.y;
setColor = displayPicker.GetPixel(a - positionLeft, -(b - positionTop));
lastSetColor = setColor;
}
saturationSlider = GUI.VerticalSlider(new Rect(positionLeft + textureWidth + 3, positionTop, 10, textureHeight), saturationSlider, 1, -1);
setColor = lastSetColor + new Color(saturationSlider, saturationSlider, saturationSlider);
GUI.Box(new Rect(positionLeft + textureWidth + 20, positionTop, 20, textureHeight), saturationTexture);
if (GUI.Button(new Rect(positionLeft + textureWidth - 60, positionTop + textureHeight + 10, 60, 25), "Apply")) {
setColor = styleTexture.GetPixel(0, 0);
// hide picker
showPicker = false;
}
// color display
GUIStyle style = new GUIStyle();
styleTexture.SetPixel(0, 0, setColor);
styleTexture.Apply();
style.normal.background = styleTexture;
GUI.Box(new Rect(positionLeft + textureWidth + 10, positionTop + textureHeight + 10, 30, 30), new GUIContent(""), style);
}
}
You can also find this helpfull asset store packages 1 2.
I recently started making a minecraft mod and i have a question regarding GUI's I want to create a slide, the only issue is the ButtonList.add(new GUISlider());
I don't understand the parameters, can someone explain them to me?
Thanks! :D
ok, I figured it out, you need to create a separate slider other than the default, then call it from there.
My Slider Code:
package tutorial.generic;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.renderer.GlStateManager;
import org.lwjgl.opengl.GL11;
public class GuiSliderFixed extends GuiButton {
public float sliderValue = 1.0F;
public float sliderMaxValue = 1.0F;
public float sliderMinValue = 1.0F;
public boolean dragging = false;
public String label;
public GuiSliderFixed(int id, int x, int y, String label, float startingValue, float maxValue, float minValue) {
super(id, x, y, 150, 20, label);
this.label = label;
this.sliderValue = startingValue;
this.sliderMaxValue = maxValue;
this.sliderMinValue = minValue;
}
protected int getHoverState(boolean par1) {
return 0;
}
#Override
public void drawButton(Minecraft mc, int mouseX, int mouseY)
{
if (this.visible)
{
FontRenderer fontrenderer = mc.fontRendererObj;
mc.getTextureManager().bindTexture(buttonTextures);
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
this.hovered = mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.width && mouseY < this.yPosition + this.height;
int k = this.getHoverState(this.hovered);
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
GlStateManager.blendFunc(770, 771);
this.drawTexturedModalRect(this.xPosition, this.yPosition, 0, 46 + k * 20, this.width / 2, this.height);
this.drawTexturedModalRect(this.xPosition + this.width / 2, this.yPosition, 200 - this.width / 2, 46 + k * 20, this.width / 2, this.height);
this.mouseDragged(mc, mouseX, mouseY);
int l = 14737632;
if (packedFGColour != 0)
{
l = packedFGColour;
}
else if (!this.enabled)
{
l = 10526880;
}
else if (this.hovered)
{
l = 16777120;
}
this.drawCenteredString(fontrenderer, this.displayString, this.xPosition + this.width / 2, this.yPosition + (this.height - 8) / 2, l);
}
}
protected void mouseDragged(Minecraft par1Minecraft, int par2, int par3) {
if (this.enabled && this.visible && this.packedFGColour == 0) {
if (this.dragging) {
this.sliderValue = (float) (par2 - (this.xPosition + 4)) / (float) (this.width - 8);
if (this.sliderValue < 0.0F) {
this.sliderValue = 0.0F;
}
if (this.sliderValue > 1.0F) {
this.sliderValue = 1.0F;
}
}
this.displayString = label + ": " + (int) (sliderValue * sliderMaxValue);
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
this.drawTexturedModalRect(this.xPosition + (int) (this.sliderValue * (float) (this.width - 8)), this.yPosition, 0, 66, 4, 20);
this.drawTexturedModalRect(this.xPosition + (int) (this.sliderValue * (float) (this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20);
}
}
public boolean mousePressed(Minecraft par1Minecraft, int par2, int par3) {
if (super.mousePressed(par1Minecraft, par2, par3)) {
this.sliderValue = (float) (par2 - (this.xPosition + 4)) / (float) (this.width - 8);
if (this.sliderValue < 0.0F) {
this.sliderValue = 0.0F;
}
if (this.sliderValue > 1.0F) {
this.sliderValue = 1.0F;
}
this.dragging = true;
return true;
} else {
return false;
}
}
public void mouseReleased(int par1, int par2) {
this.dragging = false;
}
}
My GUI Code:
package tutorial.generic;
import java.awt.Color;
import java.io.IOException;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiSlider;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StatCollector;
import net.minecraft.world.World;
public class guiGenericTileEntity extends GuiScreen{
public static NBTTagCompound nameTag = new NBTTagCompound();
private int x, y, z;
private EntityPlayer player;
private World world;
private int xSize, ySize;
public static GuiTextField textField;
public static GuiSliderFixed mySlider;
public static NBTTagCompound maxSpeedTag = new NBTTagCompound();
public guiGenericTileEntity(EntityPlayer player, World world, int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
this.player = player;
this.world = world;
xSize = 176;
ySize = 214;
}
private ResourceLocation backgroundimage = new ResourceLocation(Generic.MODID.toLowerCase() + ":" + "textures/gui/guiBackGenericTileEntity.png");
#Override
public void drawScreen(int mouseX, int mouseY, float renderPartialTicks) {
this.mc.getTextureManager().bindTexture(backgroundimage);
int x = (this.width - xSize) / 2;
int y = (this.height - ySize) / 2;
drawTexturedModalRect(x, y, 0, 0, xSize, ySize);
fontRendererObj.drawString("MTTA System", (int) (width / 2 - (width / 13)), height / 15, 8);
fontRendererObj.drawString("Train Name :", (int) (width / 2 - (width / 13)), (int) (height / 7.8), 8);
fontRendererObj.drawString("Max Speed :", (int) (width / 2 - (width / 13)), (int) (height / 3.5), 8);
textField.drawTextBox();
super.drawScreen(mouseX, mouseY, renderPartialTicks);
}
#Override
public boolean doesGuiPauseGame() {
return false;
}
#Override
public void updateScreen(){
textField.updateCursorCounter();
super.updateScreen();
}
#Override
protected void keyTyped(char typedChar, int keyCode) throws IOException{
textField.textboxKeyTyped(typedChar, keyCode);
super.keyTyped(typedChar, keyCode);
}
#Override
public void initGui(){
buttonList.add(new GuiButton(1, (int) (xSize / 3 * 2.35), ySize - (ySize / 18), xSize - 20, height / 12, "Save And Close"));
mySlider = new GuiSliderFixed(3, width / 2 - 75, height / 3, "MPH ", 1.0F, 100.0F, 1.0F);
buttonList.add(mySlider);
mySlider.sliderValue = maxSpeedTag.getFloat("MaxSpeed");
textField = new GuiTextField(width / 2, fontRendererObj, width / 2 - 50, (int) (height / 6), 100, 20);
textField.setMaxStringLength(30);
textField.setText(nameTag.getString("Name"));
textField.setFocused(true);
textField.setCanLoseFocus(false);
super.initGui();
}
#Override
protected void actionPerformed(GuiButton guibutton) {
//id is the id you give your button
switch(guibutton.id) {
case 1:
player.closeScreen();
nameTag.setString("Name", textField.getText());
maxSpeedTag.setFloat("MaxSpeed", mySlider.sliderValue);
genericTileEntity.processActivate(player, world);
player.addChatMessage(new ChatComponentText("Saved the current settings for " + textField.getText() + "!"));
break;
}
//Packet code here
//PacketDispatcher.sendPacketToServer(packet); //send packet
}
}
Calling the new Slider works like this:
create a new GuiSliderFixed in the GUI code ---> example: ---> GuiSliderFixed mySlider = new GuiSliderFixed();
set the Parameters to this (int id, int x, int y, String label, float statingValue, float maxValue, float minValue) ---> example: ---> GuiSliderFixed mySlider = new GuiSliderFixed(3, width / 2 - 75, height / 3, "MPH ", 1.0F, 100.0F, 1.0F);
add it to buttonList ---> example ---> buttonList.add(mySlider);
Hope This Helps!