mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 17:47:32 +00:00
526 lines
21 KiB
Java
526 lines
21 KiB
Java
package kamkeel.npcs.util;
|
|
|
|
import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint;
|
|
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData;
|
|
import net.minecraft.client.entity.EntityPlayerSP;
|
|
import net.minecraft.entity.EntityLivingBase;
|
|
import net.minecraft.util.Vec3;
|
|
import noppes.npcs.constants.EnumAnimationPart;
|
|
import noppes.npcs.controllers.data.AnimationData;
|
|
import noppes.npcs.controllers.data.Frame;
|
|
import noppes.npcs.controllers.data.FramePart;
|
|
import noppes.npcs.entity.EntityCustomNpc;
|
|
import noppes.npcs.entity.EntityNPCInterface;
|
|
import noppes.npcs.entity.data.ModelScalePart;
|
|
|
|
/**
|
|
* Utility class for calculating world positions based on anchor points.
|
|
* Supports both EntityNPCInterface and EntityPlayer with animation and model size awareness.
|
|
*/
|
|
public class AnchorPointHelper {
|
|
|
|
// Y offset correction for the local client player.
|
|
// EntityPlayerSP.posY is at feet, but the renderer adds yOffset (1.62) visually,
|
|
// so anchor positions computed from posY appear ~1.6 blocks too high for the local player.
|
|
// Remote players and NPCs don't have this discrepancy.
|
|
private static final float CLIENT_PLAYER_Y_OFFSET = -1.6F;
|
|
|
|
// Default biped model constants (in model units, 1 unit = 1/16 block)
|
|
private static final float MODEL_SCALE = 1f / 16f;
|
|
|
|
// Shoulder position relative to model origin
|
|
private static final float SHOULDER_Y = 2f;
|
|
private static final float SHOULDER_X_OFFSET = 5f; // Distance from center to shoulder
|
|
|
|
// Palm position (in arm's local space, relative to shoulder)
|
|
// Arm is 12 units long; palm is near the end
|
|
private static final float PALM_DOWN = 11f; // Distance down the arm to palm center
|
|
private static final float PALM_FORWARD = 2f; // Forward offset to palm surface (not center of hand)
|
|
private static final float PALM_INWARD = 0f; // Lateral offset (0 = centered on arm)
|
|
|
|
// Default height offsets as fraction of entity height
|
|
private static final float FRONT_HEIGHT = 0.7f;
|
|
private static final float CENTER_HEIGHT = 0.5f;
|
|
private static final float ARM_HEIGHT = 0.75f;
|
|
private static final float ABOVE_HEAD_HEIGHT = 1.2f;
|
|
private static final float CHEST_HEIGHT = 0.65f;
|
|
private static final float EYE_HEIGHT = 0.85f;
|
|
|
|
// Distance in front for FRONT anchor
|
|
private static final float DEFAULT_FRONT_DISTANCE = 1.0f;
|
|
|
|
// Fallback arm positioning (when no animation, arms hang at sides)
|
|
private static final float FALLBACK_LATERAL = 0.35f; // Distance from body center to arm
|
|
private static final float FALLBACK_FORWARD = 0.1f; // Slight forward offset
|
|
private static final float FALLBACK_ARM_HEIGHT = 0.45f; // Hand height when arms hang down (near hip)
|
|
|
|
/**
|
|
* Calculate world position for an anchor point on an entity.
|
|
*/
|
|
public static Vec3 calculateAnchorPosition(EntityLivingBase entity, EnergyAnchorData anchorData) {
|
|
return calculateAnchorPosition(entity, anchorData, DEFAULT_FRONT_DISTANCE);
|
|
}
|
|
|
|
/**
|
|
* Calculate world position for an anchor point with custom front offset.
|
|
*/
|
|
public static Vec3 calculateAnchorPosition(EntityLivingBase entity, EnergyAnchorData anchorData, float frontOffset) {
|
|
float scale = getModelScale(entity);
|
|
float height = entity.height;
|
|
|
|
AnchorPoint anchor = anchorData.anchorPoint;
|
|
|
|
float anchorX = anchorData.anchorOffsetX;
|
|
float anchorY = anchorData.anchorOffsetY;
|
|
float anchorZ = anchorData.anchorOffsetZ;
|
|
|
|
double x = entity.posX;
|
|
// EYE uses getEyeHeight() which already gives the correct offset from posY,
|
|
// so the client player correction would double-subtract and place it at feet level
|
|
double y = entity.posY + (anchor == AnchorPoint.EYE ? 0 : getClientPlayerYCorrection(entity));
|
|
double z = entity.posZ;
|
|
|
|
float headYaw = (float) Math.toRadians(entity.rotationYawHead);
|
|
float bodyYaw = (float) Math.toRadians(entity.renderYawOffset);
|
|
|
|
switch (anchor) {
|
|
case FRONT:
|
|
Vec3 look = entity.getLookVec();
|
|
x += look.xCoord * frontOffset * scale;
|
|
y += height * FRONT_HEIGHT + look.yCoord * frontOffset * scale;
|
|
z += look.zCoord * frontOffset * scale;
|
|
break;
|
|
|
|
case CENTER:
|
|
y += height * CENTER_HEIGHT;
|
|
break;
|
|
|
|
case RIGHT_HAND:
|
|
return calculateHandPosition(entity, anchorData, true, scale);
|
|
|
|
case LEFT_HAND:
|
|
return calculateHandPosition(entity, anchorData, false, scale);
|
|
|
|
case ABOVE_HEAD:
|
|
y += height * ABOVE_HEAD_HEIGHT;
|
|
break;
|
|
|
|
case CHEST:
|
|
y += height * CHEST_HEIGHT;
|
|
break;
|
|
|
|
case EYE:
|
|
y += entity.getEyeHeight();
|
|
break;
|
|
}
|
|
|
|
// Rotate offsets relative to entity's yaw
|
|
// EYE anchor uses head yaw so offsets follow the look direction
|
|
// All other anchors use body yaw
|
|
// +X = entity's right, +Y = up, +Z = entity's forward
|
|
float offsetYaw = (anchor == AnchorPoint.EYE) ? headYaw : bodyYaw;
|
|
Vec3 rotatedOffset = rotateOffsetByYaw(anchorX * scale, anchorY * scale, anchorZ * scale, offsetYaw);
|
|
x += rotatedOffset.xCoord;
|
|
y += rotatedOffset.yCoord;
|
|
z += rotatedOffset.zCoord;
|
|
|
|
// Apply FULL_MODEL rotation if active
|
|
FramePart fullModel = getFullModelPart(entity);
|
|
if (fullModel != null) {
|
|
double dx = x - entity.posX;
|
|
double dy = y - entity.posY;
|
|
double dz = z - entity.posZ;
|
|
Vec3 rotated = applyFullModelToWorldOffset(fullModel, entity, dx, dy, dz, bodyYaw, scale);
|
|
return Vec3.createVectorHelper(
|
|
entity.posX + rotated.xCoord,
|
|
entity.posY + rotated.yCoord,
|
|
entity.posZ + rotated.zCoord
|
|
);
|
|
}
|
|
|
|
return Vec3.createVectorHelper(x, y, z);
|
|
}
|
|
|
|
/**
|
|
* Get model scale factor for an entity.
|
|
* NPCs have configurable model size, players use default scale.
|
|
*/
|
|
private static float getModelScale(EntityLivingBase entity) {
|
|
if (entity instanceof EntityNPCInterface) {
|
|
int modelSize = ((EntityNPCInterface) entity).display.modelSize;
|
|
return modelSize / 5f; // modelSize 5 = 100% scale
|
|
}
|
|
return 1f;
|
|
}
|
|
|
|
/**
|
|
* Returns Y offset correction for the local client player.
|
|
* On the client, EntityPlayerSP.posY is at feet level but the renderer adds yOffset (1.62),
|
|
* causing anchor positions to appear ~1.6 blocks too high. This matches DBC's correction.
|
|
*/
|
|
private static double getClientPlayerYCorrection(EntityLivingBase entity) {
|
|
if (entity.worldObj.isRemote && entity instanceof EntityPlayerSP) {
|
|
return CLIENT_PLAYER_Y_OFFSET;
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
// Default arm scale (no scaling)
|
|
private static final ModelScalePart DEFAULT_ARM_SCALE = new ModelScalePart();
|
|
|
|
/**
|
|
* Get arm scale for an entity.
|
|
* EntityCustomNpc has per-part scaling, others use default (1,1,1).
|
|
*/
|
|
private static ModelScalePart getArmScale(EntityLivingBase entity) {
|
|
if (entity instanceof EntityCustomNpc) {
|
|
return ((EntityCustomNpc) entity).modelData.modelScale.arms;
|
|
}
|
|
return DEFAULT_ARM_SCALE;
|
|
}
|
|
|
|
/**
|
|
* Calculate hand position using animation data if available.
|
|
* Applies FULL_MODEL rotation if an active animation has it.
|
|
*/
|
|
private static Vec3 calculateHandPosition(EntityLivingBase entity, EnergyAnchorData anchor, boolean rightHand, float scale) {
|
|
// Try to get animated position first
|
|
Vec3 pos = getAnimatedHandPosition(entity, anchor, rightHand, scale);
|
|
if (pos == null) {
|
|
// Fallback to static position
|
|
pos = calculateFallbackHandPosition(entity, anchor, rightHand, scale);
|
|
}
|
|
|
|
// Apply FULL_MODEL rotation if active
|
|
FramePart fullModel = getFullModelPart(entity);
|
|
if (fullModel != null) {
|
|
double dx = pos.xCoord - entity.posX;
|
|
double dy = pos.yCoord - entity.posY;
|
|
double dz = pos.zCoord - entity.posZ;
|
|
float bodyYaw = (float) Math.toRadians(entity.renderYawOffset);
|
|
Vec3 rotated = applyFullModelToWorldOffset(fullModel, entity, dx, dy, dz, bodyYaw, scale);
|
|
return Vec3.createVectorHelper(
|
|
entity.posX + rotated.xCoord,
|
|
entity.posY + rotated.yCoord,
|
|
entity.posZ + rotated.zCoord
|
|
);
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
/**
|
|
* Calculate hand position from animation data.
|
|
* Works for both NPCs and Players.
|
|
*/
|
|
private static Vec3 getAnimatedHandPosition(EntityLivingBase entity, EnergyAnchorData anchor, boolean rightHand, float scale) {
|
|
AnimationData animData = AnimationData.getData(entity);
|
|
if (animData == null || !animData.isActive() || animData.animation == null) {
|
|
return null;
|
|
}
|
|
|
|
Frame frame = (Frame) animData.animation.currentFrame();
|
|
if (frame == null) {
|
|
return null;
|
|
}
|
|
|
|
EnumAnimationPart armPart = rightHand ? EnumAnimationPart.RIGHT_ARM : EnumAnimationPart.LEFT_ARM;
|
|
FramePart part = frame.frameParts.get(armPart);
|
|
if (part == null) {
|
|
return null;
|
|
}
|
|
|
|
// Get arm scale for EntityCustomNpc
|
|
ModelScalePart armScale = getArmScale(entity);
|
|
|
|
// Get rotation and pivot values
|
|
float[] rotations = getRotations(part, entity);
|
|
float[] pivots = getPivots(part, entity);
|
|
|
|
// Shoulder position in model space
|
|
float shoulderX = rightHand ? -SHOULDER_X_OFFSET : SHOULDER_X_OFFSET;
|
|
float shoulderY = SHOULDER_Y;
|
|
float shoulderZ = 0f;
|
|
|
|
// Add pivot offsets
|
|
shoulderX += pivots[0];
|
|
shoulderY += pivots[1];
|
|
shoulderZ += pivots[2];
|
|
|
|
// Palm offset in arm's local space (before rotation)
|
|
// Apply arm scaling to the palm position
|
|
float palmX = (rightHand ? -PALM_INWARD : PALM_INWARD) * armScale.scaleX;
|
|
float palmY = PALM_DOWN * armScale.scaleY;
|
|
float palmZ = PALM_FORWARD * armScale.scaleZ;
|
|
|
|
// Apply arm rotations (order: X -> Y -> Z, matching GL's reverse application)
|
|
double hx = palmX, hy = palmY, hz = palmZ;
|
|
|
|
// X rotation first
|
|
double cosX = Math.cos(rotations[0]);
|
|
double sinX = Math.sin(rotations[0]);
|
|
double newY = hy * cosX - hz * sinX;
|
|
double newZ = hy * sinX + hz * cosX;
|
|
hy = newY;
|
|
hz = newZ;
|
|
|
|
// Y rotation second
|
|
double cosY = Math.cos(rotations[1]);
|
|
double sinY = Math.sin(rotations[1]);
|
|
double newX = hx * cosY + hz * sinY;
|
|
newZ = -hx * sinY + hz * cosY;
|
|
hx = newX;
|
|
hz = newZ;
|
|
|
|
// Z rotation last
|
|
double cosZ = Math.cos(rotations[2]);
|
|
double sinZ = Math.sin(rotations[2]);
|
|
newX = hx * cosZ - hy * sinZ;
|
|
newY = hx * sinZ + hy * cosZ;
|
|
hx = newX;
|
|
hy = newY;
|
|
|
|
// Combine shoulder + rotated hand offset
|
|
double modelX = shoulderX + hx;
|
|
double modelY = shoulderY + hy;
|
|
double modelZ = shoulderZ + hz;
|
|
|
|
// Convert to blocks and apply model scale
|
|
double blockX = modelX * MODEL_SCALE * scale;
|
|
double blockY = modelY * MODEL_SCALE * scale;
|
|
double blockZ = modelZ * MODEL_SCALE * scale;
|
|
|
|
// Apply body yaw rotation
|
|
// Renderer applies glRotatef(180 - yaw) then glScalef(-1, -1, 1).
|
|
// Combined transform: x' = mx*cos(yaw) + mz*sin(yaw), z' = mx*sin(yaw) - mz*cos(yaw)
|
|
float bodyYaw = (float) Math.toRadians(entity.renderYawOffset);
|
|
double cosYaw = Math.cos(bodyYaw);
|
|
double sinYaw = Math.sin(bodyYaw);
|
|
|
|
double worldOffsetX = blockX * cosYaw + blockZ * sinYaw;
|
|
double worldOffsetZ = blockX * sinYaw - blockZ * cosYaw;
|
|
|
|
// Calculate shoulder height based on entity and scale
|
|
float shoulderHeight = entity.height * ARM_HEIGHT;
|
|
|
|
// Final world position
|
|
double worldX = entity.posX + worldOffsetX;
|
|
double worldY = entity.posY + getClientPlayerYCorrection(entity) + shoulderHeight - blockY;
|
|
double worldZ = entity.posZ + worldOffsetZ;
|
|
|
|
// Rotate offsets relative to entity's body yaw
|
|
Vec3 rotatedOffset = rotateOffsetByYaw(anchor.anchorOffsetX * scale, anchor.anchorOffsetY * scale, anchor.anchorOffsetZ * scale, bodyYaw);
|
|
worldX += rotatedOffset.xCoord;
|
|
worldY += rotatedOffset.yCoord;
|
|
worldZ += rotatedOffset.zCoord;
|
|
|
|
return Vec3.createVectorHelper(worldX, worldY, worldZ);
|
|
}
|
|
|
|
/**
|
|
* Get rotation values from FramePart.
|
|
* Uses interpolated values on client, raw values on server.
|
|
*/
|
|
private static float[] getRotations(FramePart part, EntityLivingBase entity) {
|
|
if (entity.worldObj.isRemote) {
|
|
// Client side - use interpolated values (already in radians)
|
|
part.interpolateAngles();
|
|
return part.prevRotations;
|
|
} else {
|
|
// Server side - convert raw degrees to radians
|
|
float pi = (float) Math.PI / 180f;
|
|
return new float[]{
|
|
part.rotation[0] * pi,
|
|
part.rotation[1] * pi,
|
|
part.rotation[2] * pi
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get pivot values from FramePart.
|
|
* Uses interpolated values on client, raw values on server.
|
|
*/
|
|
private static float[] getPivots(FramePart part, EntityLivingBase entity) {
|
|
if (entity.worldObj.isRemote) {
|
|
// Client side - use interpolated values
|
|
part.interpolateOffset();
|
|
return part.prevPivots;
|
|
} else {
|
|
// Server side - use raw values
|
|
return part.pivot;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate fallback hand position when no animation is active.
|
|
* Represents arms hanging at sides in default idle pose.
|
|
*/
|
|
private static Vec3 calculateFallbackHandPosition(EntityLivingBase entity, EnergyAnchorData anchor, boolean rightHand, float scale) {
|
|
float bodyYaw = (float) Math.toRadians(entity.renderYawOffset);
|
|
|
|
// Get arm scale for EntityCustomNpc
|
|
ModelScalePart armScale = getArmScale(entity);
|
|
|
|
// Perpendicular direction for lateral offset (right = +90°, left = -90° from body facing)
|
|
float perpYaw = bodyYaw + (rightHand ? (float) (Math.PI / 2) : (float) (-Math.PI / 2));
|
|
|
|
// Calculate lateral offset - arms hang at sides
|
|
// Scale by model size, but not by arm scale (lateral distance is fixed by skeleton)
|
|
double lateralX = -Math.sin(perpYaw) * FALLBACK_LATERAL * scale;
|
|
double lateralZ = Math.cos(perpYaw) * FALLBACK_LATERAL * scale;
|
|
|
|
// Small forward offset
|
|
double forwardX = -Math.sin(bodyYaw) * FALLBACK_FORWARD * scale;
|
|
double forwardZ = Math.cos(bodyYaw) * FALLBACK_FORWARD * scale;
|
|
|
|
// Arm length affects vertical position - longer arms = lower hands
|
|
double armLengthOffset = armScale.scaleY * 0.3 * scale;
|
|
|
|
double x = entity.posX + lateralX + forwardX;
|
|
double y = entity.posY + getClientPlayerYCorrection(entity) + entity.height * FALLBACK_ARM_HEIGHT + armLengthOffset;
|
|
double z = entity.posZ + lateralZ + forwardZ;
|
|
|
|
// Rotate offsets relative to entity's body yaw
|
|
Vec3 rotatedOffset = rotateOffsetByYaw(anchor.anchorOffsetX * scale, anchor.anchorOffsetY * scale, anchor.anchorOffsetZ * scale, bodyYaw);
|
|
x += rotatedOffset.xCoord;
|
|
y += rotatedOffset.yCoord;
|
|
z += rotatedOffset.zCoord;
|
|
|
|
return Vec3.createVectorHelper(x, y, z);
|
|
}
|
|
|
|
/**
|
|
* Get default height multiplier for an anchor point.
|
|
*/
|
|
public static float getHeightMultiplier(AnchorPoint anchor) {
|
|
switch (anchor) {
|
|
case FRONT:
|
|
return FRONT_HEIGHT;
|
|
case CENTER:
|
|
return CENTER_HEIGHT;
|
|
case RIGHT_HAND:
|
|
case LEFT_HAND:
|
|
return ARM_HEIGHT;
|
|
case ABOVE_HEAD:
|
|
return ABOVE_HEAD_HEIGHT;
|
|
case CHEST:
|
|
return CHEST_HEIGHT;
|
|
case EYE:
|
|
return EYE_HEIGHT;
|
|
default:
|
|
return FRONT_HEIGHT;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rotate an offset vector by the entity's body yaw so offsets are entity-relative.
|
|
* +X = entity's right, +Y = up, +Z = entity's forward.
|
|
*
|
|
* @param bodyYawRad body yaw in radians
|
|
*/
|
|
private static Vec3 rotateOffsetByYaw(double offsetX, double offsetY, double offsetZ, float bodyYawRad) {
|
|
double cos = Math.cos(bodyYawRad);
|
|
double sin = Math.sin(bodyYawRad);
|
|
double worldX = -offsetX * cos - offsetZ * sin;
|
|
double worldZ = -offsetX * sin + offsetZ * cos;
|
|
return Vec3.createVectorHelper(worldX, offsetY, worldZ);
|
|
}
|
|
|
|
// ==================== FULL_MODEL ANIMATION SUPPORT ====================
|
|
|
|
/**
|
|
* Get the FULL_MODEL FramePart if the entity has an active animation with it.
|
|
* Returns null if no FULL_MODEL animation is active.
|
|
*/
|
|
private static FramePart getFullModelPart(EntityLivingBase entity) {
|
|
AnimationData animData = AnimationData.getData(entity);
|
|
if (animData == null || !animData.isActive() || animData.animation == null) {
|
|
return null;
|
|
}
|
|
Frame frame = (Frame) animData.animation.currentFrame();
|
|
if (frame == null) {
|
|
return null;
|
|
}
|
|
FramePart part = frame.frameParts.get(EnumAnimationPart.FULL_MODEL);
|
|
return part;
|
|
}
|
|
|
|
/**
|
|
* Apply FULL_MODEL rotation to a world-space offset from entity position.
|
|
* Converts the offset to model space, applies FULL_MODEL rotation and pivots,
|
|
* then converts back to world space.
|
|
* <p>
|
|
* The rendering pipeline is: entity_pos * body_yaw * scale(-1,-1,1) * FULL_MODEL * parts
|
|
* Combined body_yaw + scale maps model space to world space:
|
|
* worldX = mx*cos(yaw) + mz*sin(yaw)
|
|
* worldY = -my (model Y-down → world Y-up)
|
|
* worldZ = mx*sin(yaw) - mz*cos(yaw)
|
|
* This transform is its own inverse for X,Z.
|
|
* <p>
|
|
* FULL_MODEL rotation order matches renderer: vertex sees Z, then Y, then X.
|
|
*/
|
|
private static Vec3 applyFullModelToWorldOffset(FramePart fullModel, EntityLivingBase entity,
|
|
double worldDX, double worldDY, double worldDZ,
|
|
float bodyYaw, float scale) {
|
|
float[] rotations = getRotations(fullModel, entity);
|
|
float[] pivots = getPivots(fullModel, entity);
|
|
|
|
// Quick exit if no transform needed
|
|
boolean hasRotation = rotations[0] != 0 || rotations[1] != 0 || rotations[2] != 0;
|
|
boolean hasPivot = pivots[0] != 0 || pivots[1] != 0 || pivots[2] != 0;
|
|
if (!hasRotation && !hasPivot) {
|
|
return Vec3.createVectorHelper(worldDX, worldDY, worldDZ);
|
|
}
|
|
|
|
// Convert world offset to model space
|
|
// Body yaw + scale combined transform is an involution (self-inverse) for X,Z
|
|
double cosYaw = Math.cos(bodyYaw);
|
|
double sinYaw = Math.sin(bodyYaw);
|
|
|
|
double mx = worldDX * cosYaw + worldDZ * sinYaw;
|
|
double mz = worldDX * sinYaw - worldDZ * cosYaw;
|
|
double my = -worldDY; // Invert Y (world Y-up → model Y-down)
|
|
|
|
// Apply FULL_MODEL rotations (vertex order: Z first, then Y, then X)
|
|
// Matches renderer: glRotatef(X), glRotatef(Y), glRotatef(Z) → vertex sees Z,Y,X
|
|
|
|
// Z rotation
|
|
double cosZ = Math.cos(rotations[2]);
|
|
double sinZ = Math.sin(rotations[2]);
|
|
double nx = mx * cosZ - my * sinZ;
|
|
double ny = mx * sinZ + my * cosZ;
|
|
mx = nx;
|
|
my = ny;
|
|
|
|
// Y rotation
|
|
double cosY = Math.cos(rotations[1]);
|
|
double sinY = Math.sin(rotations[1]);
|
|
nx = mx * cosY + mz * sinY;
|
|
double nz = -mx * sinY + mz * cosY;
|
|
mx = nx;
|
|
mz = nz;
|
|
|
|
// X rotation
|
|
double cosX = Math.cos(rotations[0]);
|
|
double sinX = Math.sin(rotations[0]);
|
|
ny = my * cosX - mz * sinX;
|
|
nz = my * sinX + mz * cosX;
|
|
my = ny;
|
|
mz = nz;
|
|
|
|
// Apply pivots in model space (after rotation, matching GL vertex ordering)
|
|
// Renderer: glTranslatef(px, -py, pz) → pivot Y is negated for model Y-down space
|
|
mx += pivots[0] * MODEL_SCALE * scale;
|
|
my -= pivots[1] * MODEL_SCALE * scale;
|
|
mz += pivots[2] * MODEL_SCALE * scale;
|
|
|
|
// Convert back to world space (same involution for X,Z; invert Y back)
|
|
double newDX = mx * cosYaw + mz * sinYaw;
|
|
double newDZ = mx * sinYaw - mz * cosYaw;
|
|
double newDY = -my;
|
|
|
|
return Vec3.createVectorHelper(newDX, newDY, newDZ);
|
|
}
|
|
}
|