Custom Mesh vertex rotation

I am building a minecraft clone and using the jme3 cubes plugin by @destroflyer . I’m implementing a torch block and I have it working just fine if the orientation is standing straight up. See image below. However, I’m trying to now make it so that the torch is rotated at a 45 degree angle and appears attached to it’s neighbor block.

The rotation is what I’m getting stuck on. Since I have to rotate the coordinates before adding to the mesh I don’t have the option of using the Geometry.rotate method. I only have the mesh vertex coordinates. Below is the code and the screenshot of the way it is standing upright. I have tried writing my own rotate method for each of the vertexes using the formula here http://en.wikipedia.org/wiki/Rotation_%28mathematics%29 but the torch is no longer visible when I do that.

Any pointers on math or utilities for doing this would be appreciated. I’m sure the code is in jme3 somewhere because ultimately that must be what it is doing behind the scenes for rotating Spatials.

Here’s the code:

``````public class BlockShape_Torch extends BlockShape{

public BlockShape_Torch(){
this.extents = new float[]{0.08f, 0.5f, 0.05f, 0.05f, 0.05f, 0.05f};
}

//{top, bottom, left, right, front, back}
private float[] extents;

@Override
public void addTo(BlockChunkControl chunk, Vector3Int blockLocation){

Block block = chunk.getBlock(blockLocation);
Vector3f blockLocation3f = new Vector3f(blockLocation.getX(), blockLocation.getY(), blockLocation.getZ());
Vector3f faceLoc_Bottom_TopLeft = blockLocation3f.add(new Vector3f((0.5f - extents[2]), (0.5f - extents[1]), (0.5f - extents[5])));
Vector3f faceLoc_Bottom_TopRight = blockLocation3f.add(new Vector3f((0.5f + extents[3]), (0.5f - extents[1]), (0.5f - extents[5])));
Vector3f faceLoc_Bottom_BottomLeft = blockLocation3f.add(new Vector3f((0.5f - extents[2]), (0.5f - extents[1]), (0.5f + extents[4])));
Vector3f faceLoc_Bottom_BottomRight = blockLocation3f.add(new Vector3f((0.5f + extents[3]), (0.5f - extents[1]), (0.5f + extents[4])));
Vector3f faceLoc_Top_TopLeft = blockLocation3f.add(new Vector3f((0.5f - extents[2]), (0.5f + extents[0]), (0.5f - extents[5])));
Vector3f faceLoc_Top_TopRight = blockLocation3f.add(new Vector3f((0.5f + extents[3]), (0.5f + extents[0]), (0.5f - extents[5])));
Vector3f faceLoc_Top_BottomLeft = blockLocation3f.add(new Vector3f((0.5f - extents[2]), (0.5f + extents[0]), (0.5f + extents[4])));
Vector3f faceLoc_Top_BottomRight = blockLocation3f.add(new Vector3f((0.5f + extents[3]), (0.5f + extents[0]), (0.5f + extents[4])));

}
}
}
}
}
}
}

private void addFaceIndices(List<Short> indices, int offset){
}

private void addSquareNormals(List<Float> normals, float normalX, float normalY, float normalZ){
for(int i=0;i<4;i++){
}
}

private void addTextureCoordinates(BlockChunkControl chunk, List<Vector2f> textureCoordinates, BlockSkin_TextureLocation textureLocation){
float xOffset = 14.0f/32.0f;
float topYOffset = 12/32.0f;

Vector2f v1 = getTextureCoordinates(chunk, textureLocation, 0 + xOffset, 0);
Vector2f v2 = getTextureCoordinates(chunk, textureLocation, 1 - xOffset, 0);
Vector2f v3 = getTextureCoordinates(chunk, textureLocation, 0 + xOffset, 1 - topYOffset);
Vector2f v4 = getTextureCoordinates(chunk, textureLocation, 1 - xOffset, 1 - topYOffset);
}

private void addTopTextureCoordinates(BlockChunkControl chunk, List<Vector2f> textureCoordinates, BlockSkin_TextureLocation textureLocation){
float xOffset = 14.0f/32.0f;
float topYOffset = 12/32.0f;
float bottomYOffset = 16/32.0f;

Vector2f v1 = getTextureCoordinates(chunk, textureLocation, 0 + xOffset, 0 + bottomYOffset);
Vector2f v2 = getTextureCoordinates(chunk, textureLocation, 1 - xOffset, 0 + bottomYOffset);
Vector2f v3 = getTextureCoordinates(chunk, textureLocation, 0 + xOffset, 1 - topYOffset);
Vector2f v4 = getTextureCoordinates(chunk, textureLocation, 1 - xOffset, 1 - topYOffset);
}

@Override
protected boolean canBeMerged(Block.Face face){
boolean isAllowed = true;
Block.Face oppositeFace = BlockNavigator.getOppositeFace(face);
for(int i=0;i<extents.length;i++){
if((i != oppositeFace.ordinal()) && (extents[i] != 0.5f)){
isAllowed = false;
break;
}
}
return isAllowed;
}
``````

}

Here is the screenshot of what the code produces:

Hey @jchappelle,

as you mentioned, you’re not working with separated geometries when using Cubes but a merged large mesh. Of course, you will have to calculate the new torch vertex coordinates (e.g. hanging sideways instead of standing) on your own and have to pass them to Cubes. There’s not really much to explain I guess.

If you want to avoid calculating with matrices (easy way to rotate points), you can honestly just set specify “hardcoded” vertices - This will mostly make even more sense than just rotating the whole thing, because the torch (in real life) would be positioned in another way to the wall in comparison to the floor. See awesome ASCII art:

Standing: |
Hanging: I/
(Notice how the torch isn’t reaching inside the wall when hanging, so it is actually another structure)

So… You will have to check in which way the torch is connected to other blocks and then use separate vertices. How you calculate them, is up to you - An automated rotation approach would most likely contain rotation matrices (sin, cos), but I think just directly specifying the result in the code should be reasonable too (considering the simple structure of a torch).

Hope, this helps.

Thanks @destroflyer. You mention “How you calculate them, is up to you” but that’s the part I’m needing help with. Your framework is doing what it is supposed to do.

I’m closer now. I have it rotating. But the position gets altered when I rotate it. Any ideas?

Code:

``````        Quaternion q = new Quaternion();
Matrix3f rotation = q.toRotationMatrix();
faceLoc_Bottom_TopLeft = rotation.mult(faceLoc_Bottom_TopLeft);
faceLoc_Bottom_TopRight = rotation.mult(faceLoc_Bottom_TopRight);
faceLoc_Bottom_BottomLeft = rotation.mult(faceLoc_Bottom_BottomLeft);
faceLoc_Bottom_BottomRight = rotation.mult(faceLoc_Bottom_BottomRight);
faceLoc_Top_TopLeft = rotation.mult(faceLoc_Top_TopLeft);
faceLoc_Top_TopRight = rotation.mult(faceLoc_Top_TopRight);
faceLoc_Top_BottomLeft = rotation.mult(faceLoc_Top_BottomLeft);
faceLoc_Top_BottomRight = rotation.mult(faceLoc_Top_BottomRight);
``````

Screenshot:

General math goes like this:
-calculate point relative to object origin
-rotate point with quaternion mult

@pspeed I think I have the first two steps working. However I’m confused about the last step. If I “add” the original location to the resulting rotation vector it seems that my object will get even farther away. Do you literally mean rotatedVector.addLocal(originalVector) ?

@pspeed nevermind. I see what you are saying. I’m not doing the first step as you described. I need to calculate the offset from the origin, then rotate the offset, then add that to the origin. Makes sense. Thank you! If I can get it I will post my result for future googlers.

There are two “tricks” that will make you a math genius in the world of 3D graphics as compared to 80% of everyone else.

1. learn reference frames and how to move between them. Mostly local space versus world space and how to translate back and forth easily. When you can do things like easily transform from a local rotation to a world rotation (or vice versa) or generally perform a change of basis (something I still have to look up to get right) then a scene graph becomes a trivially simple tool.

2. dot product tricks. Hard to describe this one… but it’s my favorite math thing. 101 uses. For the cost of only 3 multiples and two adds, it’s a powerhouse. Even a rotation matrix multiply is just three dot products… and that alone opens up a whole world of understanding.

…but the first one is the one that will help you most with things like your original problem.

1 Like

Got it! Thanks everyone. You are awesome.

``````        Block block = chunk.getBlock(blockLocation);

Vector3f faceLoc_Bottom_TopLeft = new Vector3f((0.5f - extents[2]), (0.5f - extents[1]), (0.5f - extents[5]));
Vector3f faceLoc_Bottom_TopRight = new Vector3f((0.5f + extents[3]), (0.5f - extents[1]), (0.5f - extents[5]));
Vector3f faceLoc_Bottom_BottomLeft = new Vector3f((0.5f - extents[2]), (0.5f - extents[1]), (0.5f + extents[4]));
Vector3f faceLoc_Bottom_BottomRight = new Vector3f((0.5f + extents[3]), (0.5f - extents[1]), (0.5f + extents[4]));

Vector3f faceLoc_Top_TopLeft = new Vector3f((0.5f - extents[2]), (0.5f + extents[0]), (0.5f - extents[5]));
Vector3f faceLoc_Top_TopRight = new Vector3f((0.5f + extents[3]), (0.5f + extents[0]), (0.5f - extents[5]));
Vector3f faceLoc_Top_BottomLeft = new Vector3f((0.5f - extents[2]), (0.5f + extents[0]), (0.5f + extents[4]));
Vector3f faceLoc_Top_BottomRight = new Vector3f((0.5f + extents[3]), (0.5f + extents[0]), (0.5f + extents[4]));

Quaternion q = new Quaternion();
Matrix3f rotation = q.toRotationMatrix();

faceLoc_Bottom_TopLeft = rotation.mult(faceLoc_Bottom_TopLeft);
faceLoc_Bottom_TopRight = rotation.mult(faceLoc_Bottom_TopRight);
faceLoc_Bottom_BottomLeft = rotation.mult(faceLoc_Bottom_BottomLeft);
faceLoc_Bottom_BottomRight = rotation.mult(faceLoc_Bottom_BottomRight);
faceLoc_Top_TopLeft = rotation.mult(faceLoc_Top_TopLeft);
faceLoc_Top_TopRight = rotation.mult(faceLoc_Top_TopRight);
faceLoc_Top_BottomLeft = rotation.mult(faceLoc_Top_BottomLeft);
faceLoc_Top_BottomRight = rotation.mult(faceLoc_Top_BottomRight);

Vector3f blockLocation3f = new Vector3f(blockLocation.getX(), blockLocation.getY(), blockLocation.getZ());
``````

Screenshot:

Thanks for the pointers @pspeed. I’ve been a developer for 10 years in the banking industry but games are a whole new world.

I appreciate you taking the time to give pointers like that. I minored in Math but I don’t use matrix math in my current profession so I’m rusty. I’ll review dot products so I can them in my tool belt.

@pspeed, or anyone, I still have an issue with the rotation on the opposite face.

A picture is worth a thousand words:

The torch to the right gets translated below where I thought the rotation origin was. All I am doing is trying to rotate 45 degrees to the opposite location. Here is the code

``````    private Matrix3f getRotationMatrix(Vector3f normal)
{
Block.Face face = Block.Face.fromNormal(normal);
Quaternion q = new Quaternion();
float angle = 45.0f;

if(face == Block.Face.Left)
{
System.out.println("Left face");
}
else if(face == Block.Face.Right)
{
System.out.println("Right face");
}
else if(face == Block.Face.Front)
{
System.out.println("Front face");
}
else if(face == Block.Face.Back)
{
System.out.println("Back face");
}
return q.toRotationMatrix();
}
``````

Any ideas as to what I am doing wrong?

Thanks!

We don’t really see enough of the code to guess. Could be that the code doing the transforms is still wrong… like maybe your block locations are corner based and not centered or whatever. Hard to say.

…all by itself is a sign of strangeness as you should be able to use the quaternion directly. Converting to a matrix is a very special case and almost never needed.

@pspeed it’s a custom mesh so I’m having to rotate the coordinates directly. Here is the full code:

``````public class BlockShape_Torch extends BlockShape{

public BlockShape_Torch(){
this.extents = new float[]{0.08f, 0.5f, 0.05f, 0.05f, 0.05f, 0.05f};
}

//{top, bottom, left, right, front, back}
private float[] extents;

@Override
public void addTo(BlockChunkControl chunk, Vector3Int blockLocation){

Block block = chunk.getBlock(blockLocation);

//Calculate offsets from block origin
Vector3f faceLoc_Bottom_TopLeft = new Vector3f((0.5f - extents[2]), (0.5f - extents[1]), (0.5f - extents[5]));
Vector3f faceLoc_Bottom_TopRight = new Vector3f((0.5f + extents[3]), (0.5f - extents[1]), (0.5f - extents[5]));
Vector3f faceLoc_Bottom_BottomLeft = new Vector3f((0.5f - extents[2]), (0.5f - extents[1]), (0.5f + extents[4]));
Vector3f faceLoc_Bottom_BottomRight = new Vector3f((0.5f + extents[3]), (0.5f - extents[1]), (0.5f + extents[4]));

Vector3f faceLoc_Top_TopLeft = new Vector3f((0.5f - extents[2]), (0.5f + extents[0]), (0.5f - extents[5]));
Vector3f faceLoc_Top_TopRight = new Vector3f((0.5f + extents[3]), (0.5f + extents[0]), (0.5f - extents[5]));
Vector3f faceLoc_Top_BottomLeft = new Vector3f((0.5f - extents[2]), (0.5f + extents[0]), (0.5f + extents[4]));
Vector3f faceLoc_Top_BottomRight = new Vector3f((0.5f + extents[3]), (0.5f + extents[0]), (0.5f + extents[4]));

//Get orientation from the block state
Vector3f orientationVector = (Vector3f)chunk.getBlockStateValue(blockLocation, Torch.VAR_ORIENTATION);
``````

// if(orientationVector == null)
// {
// orientationVector = Vector3f.UNIT_Y;
// }

``````    if(!Vector3f.UNIT_Y.equals(orientationVector))
{
//Calculate rotation matrix
Matrix3f rotation = getRotationMatrix(orientationVector);

//Apply rotation matrix to offsets
faceLoc_Bottom_TopLeft = rotation.mult(faceLoc_Bottom_TopLeft);
faceLoc_Bottom_TopRight = rotation.mult(faceLoc_Bottom_TopRight);
faceLoc_Bottom_BottomLeft = rotation.mult(faceLoc_Bottom_BottomLeft);
faceLoc_Bottom_BottomRight = rotation.mult(faceLoc_Bottom_BottomRight);
faceLoc_Top_TopLeft = rotation.mult(faceLoc_Top_TopLeft);
faceLoc_Top_TopRight = rotation.mult(faceLoc_Top_TopRight);
faceLoc_Top_BottomLeft = rotation.mult(faceLoc_Top_BottomLeft);
faceLoc_Top_BottomRight = rotation.mult(faceLoc_Top_BottomRight);
}

Vector3f blockLocation3f = new Vector3f(blockLocation.getX(), blockLocation.getY(), blockLocation.getZ());

if(!Vector3f.UNIT_Y.equals(orientationVector))
{
//Slide up against the block
``````

// faceLoc_Bottom_TopLeft = faceLoc_Bottom_TopLeft.add(0.7f, 0.1f, 0);
// faceLoc_Bottom_TopRight = faceLoc_Bottom_TopRight.add(0.7f, 0.1f, 0);
// faceLoc_Bottom_BottomLeft = faceLoc_Bottom_BottomLeft.add(0.7f, 0.1f, 0);
// faceLoc_Bottom_BottomRight = faceLoc_Bottom_BottomRight.add(0.7f, 0.1f, 0);
// faceLoc_Top_TopLeft = faceLoc_Top_TopLeft.add(0.7f, 0.1f, 0);
// faceLoc_Top_TopRight = faceLoc_Top_TopRight.add(0.7f, 0.1f, 0);
// faceLoc_Top_BottomLeft = faceLoc_Top_BottomLeft.add(0.7f, 0.1f, 0);
// faceLoc_Top_BottomRight = faceLoc_Top_BottomRight.add(0.7f, 0.1f, 0);
}

``````    if(shouldFaceBeAdded(chunk, blockLocation, Block.Face.Top)){

}
}
}
}
}
}
}

private Matrix3f getRotationMatrix(Vector3f normal)
{
Block.Face face = Block.Face.fromNormal(normal);
Quaternion q = new Quaternion();
float angle = 45.0f;

if(face == Block.Face.Left)
{
System.out.println("Left face");
}
else if(face == Block.Face.Right)
{
System.out.println("Right face");
}
else if(face == Block.Face.Front)
{
System.out.println("Front face");
}
else if(face == Block.Face.Back)
{
System.out.println("Back face");
}
return q.toRotationMatrix();
}

private void addFaceIndices(List<Short> indices, int offset){
}

private void addSquareNormals(List<Float> normals, float normalX, float normalY, float normalZ){
for(int i=0;i<4;i++){
}
}

private void addTextureCoordinates(BlockChunkControl chunk, List<Vector2f> textureCoordinates, BlockSkin_TextureLocation textureLocation){
float xOffset = 14.0f/32.0f;
float topYOffset = 12/32.0f;

Vector2f v1 = getTextureCoordinates(chunk, textureLocation, 0 + xOffset, 0);
Vector2f v2 = getTextureCoordinates(chunk, textureLocation, 1 - xOffset, 0);
Vector2f v3 = getTextureCoordinates(chunk, textureLocation, 0 + xOffset, 1 - topYOffset);
Vector2f v4 = getTextureCoordinates(chunk, textureLocation, 1 - xOffset, 1 - topYOffset);
}

private void addTopTextureCoordinates(BlockChunkControl chunk, List<Vector2f> textureCoordinates, BlockSkin_TextureLocation textureLocation){
float xOffset = 14.0f/32.0f;
float topYOffset = 12/32.0f;
float bottomYOffset = 16/32.0f;

Vector2f v1 = getTextureCoordinates(chunk, textureLocation, 0 + xOffset, 0 + bottomYOffset);
Vector2f v2 = getTextureCoordinates(chunk, textureLocation, 1 - xOffset, 0 + bottomYOffset);
Vector2f v3 = getTextureCoordinates(chunk, textureLocation, 0 + xOffset, 1 - topYOffset);
Vector2f v4 = getTextureCoordinates(chunk, textureLocation, 1 - xOffset, 1 - topYOffset);
}

@Override
protected boolean canBeMerged(Block.Face face){
boolean isAllowed = true;
Block.Face oppositeFace = BlockNavigator.getOppositeFace(face);
for(int i=0;i<extents.length;i++){
if((i != oppositeFace.ordinal()) && (extents[i] != 0.5f)){
isAllowed = false;
break;
}
}
return isAllowed;
}
``````

}

…but you could have just used a quaternion for all of that and saved on the matrix creation. Plus the quat mult is faster anyway.

Second, the above line could have simply been:
Vector3f blockLocation3f = blockLocation.clone();

Finally, is blockLocation the corner of the block or the center? If it’s the corner then you still need to add back in the 0.5 offset that you based your rotation on. (and if so then I pegged the problem in my last post without even seeing the code… :))

blockLocation is the center of the block.

Block location is actually an instance of Vector3Int no Vector3f so that’s why I couldn’t use the clone() method. Vector3Int is just an integer version of Vector3f and is part of the cubes framework.

I will say that the rotation works on all sides with negative normals. If the normal is positive then it dips below the block as you see in the screenshot.

In other words, the left and the back face is working. It’s the right and front that actually translates the position below the block.

The difference is I am doing (360-45) for the angle but that seems appropriate to change the direction. I tried using -45 but that didn’t work either.

First, -45 and 360-45 will produce the same results.

Second, take a really close look at your method that gets the rotations… and tell me if you spot the difference in the cases of the faces that fail… Actually, it’s Front and Back that are different. (and wrong)

Front and back rotate about the X axis instead of Z, but that seems correct. And the back seems to work correctly. Although it could just appear to be working correctly because it is in a somewhat suitable location.

I’m sure I’m missing something, but I’ve been staring at it for a while and can’t see it.

I guess I misunderstood what you were doing but I see now. You tip the torch 45 degrees in whatever direction you want it tipped. I mistakenly thought you were tipping it once and then rotating it around Y.

At this point, if this were my own code then I’d be putting a bunch of System.out.println() calls in to see which of various assumptions is wrong.

I’ll keep plugging away at it. Thanks for taking a look.

I put a lot of println statements in the code for generating a regular cube, not a torch, to see if the block location is in fact the center. I found that my assumption was wrong. It is actually the bottom, top left corner of the block. Here is my output for one of the blocks.

Location: (1.0, 0.0, 15.0)
faceLoc_Bottom_TopLeft=(1.0, 0.0, 15.0)
faceLoc_Bottom_TopRight=(2.0, 0.0, 15.0)
faceLoc_Bottom_BottomLeft=(1.0, 0.0, 16.0)
faceLoc_Bottom_BottomRight=(2.0, 0.0, 16.0)
faceLoc_Top_TopLeft=(1.0, 1.0, 15.0)
faceLoc_Top_TopRight=(2.0, 1.0, 15.0)
faceLoc_Top_BottomLeft=(1.0, 1.0, 16.0)
faceLoc_Top_BottomRight=(2.0, 1.0, 16.0)

I haven’t fixed it yet but I think that is going to be it.

If that’s it then you pegged the problem with your first comment.