Libgdx InputListener touchUp - input

I've added InputListener to my actor and now I want to check if touchUp event is inside the actor.
Simple example: I'm starting draging mouse inside my actor and i'm finishing outside my actor.
I though that touchUp event will start only if mouse is inside my actor but it also start outside my actor (when touchDown event start inside my actor).
How to check if touchUp event is inside my actor only?

I see two solutions here:
To use some flag to check if the pointer is inside the actor and handle it with exit method:
image.addListener(new InputListener(){
boolean touched = false;
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
{
touched = true;
System.out.println("TOUCH DOWN");
return true;
}
#Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button)
{
if(touched)
{
touched = false;
System.out.println("TOUCH UP");
}
}
#Override
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor)
{
touched = false;
}
});
To check if pointer is inside the actor inside touchUp
#Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button)
{
Stage stage = event.getTarget().getStage();
Vector2 mouse = stage.screenToStageCoordinates( new Vector2(Gdx.input.getX(), Gdx.input.getY()) );
if(stage.hit(mouse.x, mouse.y, true) == event.getTarget())
{
System.out.println("TOUCH UP");
}
}
Both solutions need some extra code but both should be working fine.

Sorry miss read your question noticed when you changed it.
Still, I would add a listener and simply check coordinates of actor. The x and y given with a clicklistener just return local coordinates of the actor so a simple check vs width and height is enough.
ClickListener cl = new ClickListener()
{
#Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
super.touchUp(event, x, y, pointer, button);
if (x > 0 && y > 0 && x < getWidth() && y < getHeight())
{
System.out.println("Released within actor");
}
}
};
actor.addListener(cl);

Related

How to change what block is at certain coordinates?

I am trying to recreate the mod in the YouTuber TommyInnit's video "Minecraft’s Laser Eye Mod Is Hilarious" as me and my friends wish to use it but we couldn't find it on the internet, and I have taken code from here for raycasting and also set up a keybind, but I cannot figure out how to setb the block you are looking at. I have tried to manage to get the block and set it but I can only find how to make new blocks that don't yet exist. My code is the following, with the block code being on line 142:
package net.laser.eyes;
import org.lwjgl.glfw.GLFW;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.text.LiteralText;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.event.client.ClientTickCallback;
import net.fabricmc.api.*;
import net.fabricmc.fabric.api.client.rendering.v1.*;
import net.minecraft.block.*;
import net.minecraft.client.*;
import net.minecraft.client.gui.*;
import net.minecraft.client.util.math.*;
import net.minecraft.entity.*;
import net.minecraft.entity.decoration.*;
import net.minecraft.entity.projectile.*;
import net.minecraft.text.*;
import net.minecraft.util.hit.*;
import net.minecraft.util.math.*;
import net.minecraft.world.*;
public class main implements ModInitializer {
#Override
public void onInitialize() {
KeyBinding binding1 = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.laser-eyes.shoot", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_R, "key.category.laser.eyes"));
HudRenderCallback.EVENT.register(main::displayBoundingBox);
ClientTickCallback.EVENT.register(client -> {
while (binding1.wasPressed()) {
client.player.sendMessage(new LiteralText("Key 1 was pressed!"), false);
}
});
}
private static long lastCalculationTime = 0;
private static boolean lastCalculationExists = false;
private static int lastCalculationMinX = 0;
private static int lastCalculationMinY = 0;
private static int lastCalculationWidth = 0;
private static int lastCalculationHeight = 0;
private static void displayBoundingBox(MatrixStack matrixStack, float tickDelta) {
long currentTime = System.currentTimeMillis();
if(lastCalculationExists && currentTime - lastCalculationTime < 1000/45) {
drawHollowFill(matrixStack, lastCalculationMinX, lastCalculationMinY,
lastCalculationWidth, lastCalculationHeight, 2, 0xffff0000);
return;
}
lastCalculationTime = currentTime;
MinecraftClient client = MinecraftClient.getInstance();
int width = client.getWindow().getScaledWidth();
int height = client.getWindow().getScaledHeight();
Vec3d cameraDirection = client.cameraEntity.getRotationVec(tickDelta);
double fov = client.options.fov;
double angleSize = fov/height;
Vector3f verticalRotationAxis = new Vector3f(cameraDirection);
verticalRotationAxis.cross(Vector3f.POSITIVE_Y);
if(!verticalRotationAxis.normalize()) {
lastCalculationExists = false;
return;
}
Vector3f horizontalRotationAxis = new Vector3f(cameraDirection);
horizontalRotationAxis.cross(verticalRotationAxis);
horizontalRotationAxis.normalize();
verticalRotationAxis = new Vector3f(cameraDirection);
verticalRotationAxis.cross(horizontalRotationAxis);
HitResult hit = client.crosshairTarget;
if (hit.getType() == HitResult.Type.MISS) {
lastCalculationExists = false;
return;
}
int minX = width;
int maxX = 0;
int minY = height;
int maxY = 0;
for(int y = 0; y < height; y +=2) {
for(int x = 0; x < width; x+=2) {
if(minX < x && x < maxX && minY < y && y < maxY) {
continue;
}
Vec3d direction = map(
(float) angleSize,
cameraDirection,
horizontalRotationAxis,
verticalRotationAxis,
x,
y,
width,
height
);
HitResult nextHit = rayTraceInDirection(client, tickDelta, direction);//TODO make less expensive
if(nextHit == null) {
continue;
}
if(nextHit.getType() == HitResult.Type.MISS) {
continue;
}
if(nextHit.getType() != hit.getType()) {
continue;
}
if (nextHit.getType() == HitResult.Type.BLOCK) {
if(!((BlockHitResult) nextHit).getBlockPos().equals(((BlockHitResult) hit).getBlockPos())) {
continue;
}
} else if(nextHit.getType() == HitResult.Type.ENTITY) {
if(!((EntityHitResult) nextHit).getEntity().equals(((EntityHitResult) hit).getEntity())) {
continue;
}
}
if(minX > x) minX = x;
if(minY > y) minY = y;
if(maxX < x) maxX = x;
if(maxY < y) maxY = y;
}
}
lastCalculationExists = true;
lastCalculationMinX = minX;
lastCalculationMinY = minY;
lastCalculationWidth = maxX - minX;
lastCalculationHeight = maxY - minY;
drawHollowFill(matrixStack, minX, minY, maxX - minX, maxY - minY, 2, 0xffff0000);
LiteralText text = new LiteralText("Bounding " + minX + " " + minY + " " + width + " " + height + ": ");
client.player.sendMessage(text.append(getLabel(hit)), true);
//SET THE BLOCK (maybe use hit.getPos(); to find it??)
}
private static void drawHollowFill(MatrixStack matrixStack, int x, int y, int width, int height, int stroke, int color) {
matrixStack.push();
matrixStack.translate(x-stroke, y-stroke, 0);
width += stroke *2;
height += stroke *2;
DrawableHelper.fill(matrixStack, 0, 0, width, stroke, color);
DrawableHelper.fill(matrixStack, width - stroke, 0, width, height, color);
DrawableHelper.fill(matrixStack, 0, height - stroke, width, height, color);
DrawableHelper.fill(matrixStack, 0, 0, stroke, height, color);
matrixStack.pop();
}
private static Text getLabel(HitResult hit) {
if(hit == null) return new LiteralText("null");
switch (hit.getType()) {
case BLOCK:
return getLabelBlock((BlockHitResult) hit);
case ENTITY:
return getLabelEntity((EntityHitResult) hit);
case MISS:
default:
return new LiteralText("null");
}
}
private static Text getLabelEntity(EntityHitResult hit) {
return hit.getEntity().getDisplayName();
}
private static Text getLabelBlock(BlockHitResult hit) {
BlockPos blockPos = hit.getBlockPos();
BlockState blockState = MinecraftClient.getInstance().world.getBlockState(blockPos);
Block block = blockState.getBlock();
return block.getName();
}
private static Vec3d map(float anglePerPixel, Vec3d center, Vector3f horizontalRotationAxis,
Vector3f verticalRotationAxis, int x, int y, int width, int height) {
float horizontalRotation = (x - width/2f) * anglePerPixel;
float verticalRotation = (y - height/2f) * anglePerPixel;
final Vector3f temp2 = new Vector3f(center);
temp2.rotate(verticalRotationAxis.getDegreesQuaternion(verticalRotation));
temp2.rotate(horizontalRotationAxis.getDegreesQuaternion(horizontalRotation));
return new Vec3d(temp2);
}
private static HitResult rayTraceInDirection(MinecraftClient client, float tickDelta, Vec3d direction) {
Entity entity = client.getCameraEntity();
if (entity == null || client.world == null) {
return null;
}
double reachDistance = 5.0F;
HitResult target = rayTrace(entity, reachDistance, tickDelta, false, direction);
boolean tooFar = false;
double extendedReach = 6.0D;
reachDistance = extendedReach;
Vec3d cameraPos = entity.getCameraPosVec(tickDelta);
extendedReach = extendedReach * extendedReach;
if (target != null) {
extendedReach = target.getPos().squaredDistanceTo(cameraPos);
}
Vec3d vec3d3 = cameraPos.add(direction.multiply(reachDistance));
Box box = entity
.getBoundingBox()
.stretch(entity.getRotationVec(1.0F).multiply(reachDistance))
.expand(1.0D, 1.0D, 1.0D);
EntityHitResult entityHitResult = ProjectileUtil.raycast(
entity,
cameraPos,
vec3d3,
box,
(entityx) -> !entityx.isSpectator() && entityx.collides(),
extendedReach
);
if (entityHitResult == null) {
return target;
}
Entity entity2 = entityHitResult.getEntity();
Vec3d hitPos = entityHitResult.getPos();
if (cameraPos.squaredDistanceTo(hitPos) < extendedReach || target == null) {
target = entityHitResult;
if (entity2 instanceof LivingEntity || entity2 instanceof ItemFrameEntity) {
client.targetedEntity = entity2;
}
}
return target;
}
private static HitResult rayTrace(
Entity entity,
double maxDistance,
float tickDelta,
boolean includeFluids,
Vec3d direction
) {
Vec3d end = entity.getCameraPosVec(tickDelta).add(direction.multiply(maxDistance));
return entity.world.raycast(new RaycastContext(
entity.getCameraPosVec(tickDelta),
end,
RaycastContext.ShapeType.OUTLINE,
includeFluids ? RaycastContext.FluidHandling.ANY : RaycastContext.FluidHandling.NONE,
entity
));
}
}
Firstly, I heavily recommend following the standard Java naming conventions as it will make your code more understandable for others.
The technical name for a block present in the world at a specific position is a "Block State", represented by the BlockState class.
You can only change the block state at a specific position on the server-side. Your raycasting code in ran on the client-side, so you need to use the Fabric Networking API. You can see the server-side Javadoc here and the client-side Javadoc here.
Thankfully, Fabric Wiki has a networking tutorial so you don't have to read all that Javadoc. The part that you're interested in is "sending packets to the server and receiving packets on the server".
Here's a guide specific for your use case:
Introduction to Networking
Minecraft operates in two different components; the client and the server.
The client is responsible for doing jobs such as rendering and GUIs, while the server is responsible for handling the world storage, entity AI etc. (talking about logical client and server here)
The physical server and physical client are the actual JAR files that are run.
The physical (dedicated) server contains only the logical server, while a physical client contains both a logical (integrated) server and a logical client.
A diagram that explains it can be found here.
So, the logical client cannot change the state of the logical server (e.g. block states in a world), so packets have to be sent from the client to the server in order for the server to respond.
The following code is only example code, and you shouldn't copy it! You should think about safety precautions like preventing cheat clients from changing every block. Probably one of the most important rules in networking: assume the client is lying.
The Fabric Networking API
Your starting points are ServerPlayNetworking and ClientPlayNetworking. They are the classes that help you send and receive packets.
Register a listener using registerGlobalReceiver, and send a packet by using send.
You first need an Identifier in order to separate your packet from other packets and make sure it is interpreted correctly. An Identifier like this is recommended to be put in a static field in your ModInitializer or a utility class.
public class MyMod implements ModInitializer {
public static final Identifier SET_BLOCK_PACKET = new Identifier("modid", "setblock");
}
(Don't forget to replace modid with your mod ID)
You usually want to pass data with your packets (e.g. block position and block to change to), and you can do so with a PacketByteBuf.
Let's Piece This all Together
So, we have an Identifier. Let's send a packet!
Client-Side
We will start by creating a PacketByteBuf and writing the correct data:
private static void displayBoundingBox(MatrixStack matrixStack, float tickDelta) {
// ...
PacketByteBuf data = PacketByteBufs.create();
buf.writeBlockPos(hit.getPos());
// Minecraft doesn't have a way to write a Block to a packet, so we will write the registry name instead
buf.writeIdentifier(new Identifier("minecraft", "someblock" /*for example, "stone"*/));
}
And now sending the packet
// ...
ClientPlayNetworking.send(SET_BLOCK_PACKET, buf);
Server-Side
A packet with the SET_BLOCK_PACKET ID has been sent, But we also need to listen and receive it on the server-side. We can do that by using ServerPlayNetworking.registerGlobalReceiver:
#Override
public void onInitialize() {
// ...
// This code MUST be in onInitialize
ServerPlayNetworking.registerGlobalReceiver(SET_BLOCK_PACKET, (server, player, handler, buf, sender) -> {
});
}
We are using a lambda expression here. For more info about lambdas, Google is your friend.
When receiving a packet, code inside your lambda will be executed on the network thread. This code is not allowed to modify anything related to in-game logic (i.e. the world). For that, we will use server.execute(Runnable).
You should read the buf on the network thread, though.
ServerPlayNetworking.registerGlobalReceiver(SET_BLOCK_PACKET, (server, player, handler, buf, sender) -> {
BlockPos pos = buf.readBlockPos(); // reads must be done in the same order
Block blockToSet = Registry.BLOCK.get(buf.readIdentifier()); // reading using the identifier
server.execute(() -> { // We are now on the main thread
// In a normal mod, checks will be done here to prevent the client from setting blocks outside the world etc. but this is only example code
player.getServerWorld().setBlockState(pos, blockToSet.getDefaultState()); // changing the block state
});
});
Once again, you should prevent the client from sending invalid locations

Return type for the method is missing processing.js

Hi i'm new to programming, and while i was trying to do an exercise this error poped up, and i have no idea what it means and how to fix it.
it's an example in the processing.js library that after i tried to copy didn't go so well.
Mover mover;
void setup(){
size (600,600);
mover = new Mover();
}
void draw(){
mover.update();
mover.display();
mover.checkEdges();
}
class Mover {
// position, velocity, and acceleration
PVector position;
PVector velocity;
PVector acceleration;
// Mass is tied to size
float mass;
Mover(float m, float x, float y) { //<<<the error occurs here
mass = m;
position = new PVector(x, y);
velocity = new PVector(0, 0);
acceleration = new PVector(0, 0);
}
// Newton's 2nd law: F = M * A
// or A = F / M
void applyForce(PVector force) {
// Divide by mass
PVector f = PVector.div(force, mass);
// Accumulate all forces in acceleration
acceleration.add(f);
}
void update() {
// Velocity changes according to acceleration
velocity.add(acceleration);
// position changes by velocity
position.add(velocity);
// We must clear acceleration each frame
acceleration.mult(0);
}
// Draw Mover
void display() {
stroke(255);
strokeWeight(2);
fill(255, 200);
ellipse(position.x, position.y, mass*16, mass*16);
}
// Bounce off bottom of window
void checkEdges() {
if (position.y > height) {
velocity.y *= -0.9; // A little dampening when hitting the bottom
position.y = height;
}
}
}
The only problem I see in your code is that your Mover constructor takes three arguments, but you're not giving it any in this line:
mover = new Mover();
So I'd fix that error before anything else. If you're still having problems, please be much more specific about exactly how you're compiling and running this code. Are you using the Processing editor? Which version? During exactly which step do you get an error?

How to pass variables as parameters

I have two bits of code
Tree tree;
void setup() {
int SZ = 512; // screen size
int d = 2;
int x = SZ/2;
int y = SZ;
size(SZ,SZ);
background(255);
noLoop();
tree = new Tree(d, x, y);
}
void draw() {
tree.draw();
}
and also
class Tree {
// member variables
int m_lineLength; // turtle line length
int m_x; // initial x position
int m_y; // initial y position
float m_branchAngle; // turtle rotation at branch
float m_initOrientation; // initial orientation
String m_state; // initial state
float m_scaleFactor; // branch scale factor
String m_F_rule; // F-rule substitution
String m_H_rule; // H-rule substitution
String m_f_rule; // f-rule substitution
int m_numIterations; // number of times to substitute
// constructor
// (d = line length, x & y = start position of drawing)
Tree(int d, int x, int y) {
m_lineLength = d;
m_x = x;
m_y = y;
m_branchAngle = (25.7/180.0)*PI;
m_initOrientation = -HALF_PI;
m_scaleFactor = 1;
m_state = "F";
m_F_rule = "F[+F]F[-F]F";
m_H_rule = "";
m_f_rule = "";
m_numIterations = 5;
// Perform L rounds of substitutions on the initial state
for (int k=0; k < m_numIterations; k++) {
m_state = substitute(m_state);
}
}
void draw() {
pushMatrix();
pushStyle();
stroke(0);
translate(m_x, m_y); // initial position
rotate(m_initOrientation); // initial rotation
// now walk along the state string, executing the
// corresponding turtle command for each character
for (int i=0; i < m_state.length(); i++) {
turtle(m_state.charAt(i));
}
popStyle();
popMatrix();
}
// Turtle command definitions for each character in our alphabet
void turtle(char c) {
switch(c) {
case 'F': // drop through to next case
case 'H':
line(0, 0, m_lineLength, 0);
translate(m_lineLength, 0);
break;
case 'f':
translate(m_lineLength, 0);
break;
case 's':
scale(m_scaleFactor);
break;
case '-':
rotate(m_branchAngle);
break;
case '+':
rotate(-m_branchAngle);
break;
case '[':
pushMatrix();
break;
case ']':
popMatrix();
break;
default:
println("Bad character: " + c);
exit();
}
}
// apply substitution rules to string s and return the resulting string
String substitute(String s) {
String newState = new String();
for (int j=0; j < s.length(); j++) {
switch (s.charAt(j)) {
case 'F':
newState += m_F_rule;
break;
case 'H':
newState += m_F_rule;
break;
case 'f':
newState += m_f_rule;
break;
default:
newState += s.charAt(j);
}
}
return newState;
}
}
This isn't assessed homework, it's an end of chapter exercise but I'm very stuck.
I want to "extend the Tree constructor so that values for all of the Tree member variables can be passed in as parameters."
Whilst I understand what variables and parameters are, I'm very stuck as to what to begin reading / where to begin editing the code.
One thing that has confused me and made me question my understanding is that, if I change the constructor values, (for example m_numiterations = 10;), the output when the code is run is the same.
Any pointers in the right direction would be greatly appreciated.
You already have everything in there to add more stuff to your Tree.
You see, in your setup(), you call:
tree = new Tree(d, x, y);
Now, that line, is actually calling the contructor implemented here:
Tree(int d, int x, int y) {
m_lineLength = d;
m_x = x;
etc....
So, if you want you can change that constructor to accept any variable that you want to pass from setup()
For instance, Tree(int d, int x, int y, String word, float number, double bigNumber)
Try experimenting with that. If you have any questions, PM me
EDIT
Let me add a little more flavor to it:
You see constructors are the way to initialize your class. It does not matter the access level (protected, public, private) or the number of constructors.
So, for example, Let's say you have this class with two public fields:
public class Book
{
public String Author;
public String Title;
public Book(String title, String author)
{
this.Title = title;
this.Author = author;
}
public Book()
{
this("Any title");//default title
}
}
Here, you can create books with both author and title OR only title! isn't that great? You can create things that are not inclusively attached to other things!
I hope you understand this. But, basically the idea is to encapsulate everything that matters to a certain topic to its own class.
NEW EDIT
Mike, you see, according to your comment you added this line:
int m_numIterations = 25;
The thing is that what you just did was only create a variable. A variable holds the information that you eventually want to use in the program. Let's say you are in high school physics trying to solve a basic free fall problem. You have to state the gravity, don't you?
So, in your notebook, you would go:
g = 9.8 m/s^2
right? it is a constant. But, a variable that you will use in your problem.
Well, the same thing applies in programming.
You added the line. That means that now, you can use it in your problem.
Now, go to this line,
tree = new Tree(d, x, y);
and change it to:
tree = new Tree(d, x, y, m_numIterations);
As you can see, now you are ready to "use" your variable in your tree. However! you are not done yet. You have to update as well your constructor because if not, the compiler will complain!
Go to this line now,
Tree(int d, int x, int y) {
m_lineLength = d;
m_x = x;
....
And change it to:
Tree(int d, int x, int y, int iterations) {
m_lineLength = d;
m_x = x;
....
You, see, now, you are telling your tree to accept a new variable call iterations that you are setting from somewhere else.
However! Be warned! There is a little problem with this :(
You don't have any code regarding the use of that variable. So, if you are expecting to actually see something different in the Tree, it won't happen! You need to find a use to the variable within the scope of the Tree (the one that I called iterations). So, first, find a use for it! or post any more code that you have to help you solve it. If you are calling a variable iterations, it is because you are planning to use a loop somewhere, amirite? Take care man. Little steps. Be patient. I added a little more to the Books example. I forgot to explain it yesterday :p

Stage.addListener not working in libgdx

I am just trying to add a click listener to the stage in libgdx but it is not working here is my code;
stage.addListener(new InputListener() {
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
Gdx.app.log("CLICK", "LISTENER");
parallaxBackground.reverse();
return super.touchDown(event, x, y, pointer, button);
}
});
This is from my main game screen, which inherits from an abstract screen which calls,
Gdx.input.setInputProcessor(stage);
In the abstract class I also call,
stage.act(delta);
I am most definetely calling super.render() as well from the child class
What is causing this!
EDIT
I don't know if it matters but my stage has no actors but I just want to simply acknowledge a click.
Since you added no actors to your stage, your stage has no substance so it cannot be touched.
You can add create an actor that is the same size as the screen and is aligned with the screen to do what you're trying. Something like this (untested):
Actor screenActor = new Actor(){
public void act (float delta) {
super.act(delta);
Viewport viewport = getStage().getViewport();
width = viewport.getScreenWidth();
height = viewport.getScreenHeight();
x = viewport.getScreenX();
y = viewport.getScreenY();
}
};
stage.addActor(screenActor);
You would add this actor first, so other actors get first crack at intercepting touches, and this is the fallback.
Here is how I solved this problem:
In Photoshop I created a 100x100px image and put only one layer in it and set the opacity of that layer to 1%, I also removed the background (This made a totally transparent image) and saved it as .png to use it as texture.
I created an Actor and called it BgActor and drew the texture to it.
Here is the png image that I created
and here is how my Actor looks like:
The BgActor class:
public class BgActor extends Actor {
private float x, y, width, height;
private Texture texture = new Texture(Gdx.files.internal("images/transparent-bg.png"));
public BgActor(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
setTouchable(Touchable.enabled);
}
#Override
public void draw(Batch batch, float parentAlpha) {
setBounds(x, y, width, height);
batch.draw(texture, x, y, width, height);
}
public void dispose() {
texture.dispose();
}
}
Implementation (I used your Listener):
BgActor bgActor = new BgActor(0, 0, stage.getWidth(), stage.getHeight);
bgActor.addListener(new InputListener() {
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
Gdx.app.log("CLICK", "LISTENER");
parallaxBackground.reverse();
return super.touchDown(event, x, y, pointer, button);
}
});
......
stage.addActor(bgActor);
And don't forget to dispose the bgActor in your Screen's hide() method.
Good luck ..

GLUT mouse button down

I would like to zoom in on an object for as long as I hold the right mouse button down. The issue right now is that I have to click it every time I want to zoom. Is there a way I can modify my code so that it will zoom while I hold the button, rather than clicking it?
void mouse(int button, int state, int x, int y)
{
// Save the left button state
if (button == GLUT_LEFT_BUTTON)
{
leftMouseButtonDown = (state == GLUT_DOWN);
zMovement += 0.1f;
}
else if (button == GLUT_RIGHT_BUTTON)
{
leftMouseButtonDown = (state == GLUT_DOWN);
zMovement -= 0.1f;
}
// Save the mouse position
mouseXPos = x;
mouseYPos = y;
}
The state variable of your function tells you what type of mouse-button event had happened: It can either be GLUT_DOWN or GLUT_UP.
Knowing this, you can store this state in an extra variable outside of the mouse function and zoom as long as the state is set to true (this has to be done somewhere in every frame). The code could look like the following:
void mouse(int button, int state, int x, int y)
{
// Save the left button state
if (button == GLUT_LEFT_BUTTON)
{
leftMouseButtonDown = (state == GLUT_DOWN);
}
else if (button == GLUT_RIGHT_BUTTON)
{
// \/ right MouseButton
rightMouseButtonDown = (state == GLUT_DOWN);
}
// Save the mouse position
mouseXPos = x;
mouseYPos = y;
}
void callThisInEveryFrame()
{
if (leftMouseButtonDown)
zMovement += 0.1f;
if (rightMouseButtonDown)
zMovement -= 0.1f;
}