From bae567e7677aa7a89180fd1e63af97296eb09eba Mon Sep 17 00:00:00 2001 From: dldev32 Date: Tue, 12 May 2026 21:31:09 +0300 Subject: [PATCH 1/5] Added support for OBB collisions --- .../baseclasses/BoundingBox.java | 386 +++++++++++++++--- .../baseclasses/BoundingBoxHitResult.java | 26 +- .../baseclasses/VehicleGroundDeviceBox.java | 4 +- .../components/AEntityE_Interactable.java | 8 +- .../components/AEntityF_Multipart.java | 18 +- .../jsondefs/JSONCollisionBox.java | 25 +- .../jsondefs/JSONCollisionGroup.java | 25 +- .../WrapperAABBCollective.java | 2 +- .../java/mcinterface1122/WrapperWorld.java | 4 +- .../WrapperAABBCollective.java | 2 +- .../java/mcinterface1165/WrapperWorld.java | 4 +- .../WrapperAABBCollective.java | 2 +- .../java/mcinterface1182/WrapperWorld.java | 4 +- .../WrapperAABBCollective.java | 2 +- .../java/mcinterface1192/WrapperWorld.java | 4 +- .../WrapperAABBCollective.java | 2 +- .../java/mcinterface1201/WrapperWorld.java | 4 +- .../WrapperAABBCollective.java | 6 +- .../java/mcinterface1211/WrapperWorld.java | 4 +- 19 files changed, 410 insertions(+), 122 deletions(-) diff --git a/mccore/src/main/java/minecrafttransportsimulator/baseclasses/BoundingBox.java b/mccore/src/main/java/minecrafttransportsimulator/baseclasses/BoundingBox.java index 588f00131..ead473339 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/baseclasses/BoundingBox.java +++ b/mccore/src/main/java/minecrafttransportsimulator/baseclasses/BoundingBox.java @@ -32,11 +32,13 @@ * * @author don_bruce */ -public class BoundingBox { - private static final double HITBOX_CLAMP = 0.015625; - public final Point3D localCenter; - public final Point3D globalCenter; - public final Point3D currentCollisionDepth; +public class BoundingBox { + private static final double HITBOX_CLAMP = 0.015625; + private static final double OBB_EPSILON = 1.0E-7D; + public final Point3D localCenter; + public final Point3D globalCenter; + public final RotationMatrix orientation; + public final Point3D currentCollisionDepth; public final List collidingBlockPositions = new ArrayList<>(); private RenderableData wireframeRenderable; private RenderableData holographicRenderable; @@ -83,8 +85,8 @@ public BoundingBox(Point3D localCenter, Point3D globalCenter, double widthRadius /** * JSON constructor. Used for boxes that are created from JSON and need extended properties. **/ - public BoundingBox(JSONCollisionBox definition, JSONCollisionGroup groupDef) { - this(definition.pos, definition.pos.copy(), definition.width / 2D, definition.height / 2D, definition.width / 2D, definition.collidesWithLiquids, definition, groupDef, groupDef.collisionTypes); + public BoundingBox(JSONCollisionBox definition, JSONCollisionGroup groupDef) { + this(definition.pos, definition.pos.copy(), definition.width / 2D, definition.height / 2D, (definition.length != 0 ? definition.length : definition.width) / 2D, definition.collidesWithLiquids, definition, groupDef, groupDef.collisionTypes); } /** @@ -103,9 +105,10 @@ public BoundingBox(Point3D start, Point3D end) { * Master constructor. Used for main creation. **/ private BoundingBox(Point3D localCenter, Point3D globalCenter, double widthRadius, double heightRadius, double depthRadius, boolean collidesWithLiquids, JSONCollisionBox definition, JSONCollisionGroup groupDef, Set collisionTypes) { - this.localCenter = localCenter; - this.globalCenter = globalCenter; - this.tempGlobalCenter = globalCenter.copy(); + this.localCenter = localCenter; + this.globalCenter = globalCenter; + this.orientation = new RotationMatrix(); + this.tempGlobalCenter = globalCenter.copy(); this.currentCollisionDepth = new Point3D(); this.widthRadius = widthRadius; this.heightRadius = heightRadius; @@ -140,52 +143,226 @@ public boolean updateCollisions(AWrapperWorld world, Point3D offset, boolean ign * Mostly used for updating hitboxes that rotate with the entity. Rotation is done using the fine * Point3d rotation to allow for better interaction while standing on entities. */ - public void updateToEntity(AEntityD_Definable entity, Point3D optionalOffset) { - if (optionalOffset != null) { - globalCenter.set(optionalOffset); - } else { - globalCenter.set(localCenter); - } - globalCenter.multiply(entity.scale).rotate(entity.orientation).add(entity.position); - if (groupDef != null && (groupDef.collisionTypes.contains(CollisionType.ENTITY) || groupDef.collisionTypes.contains(CollisionType.VEHICLE))) { - //Need to round box to prevent floating-point errors for player and entity collision. - globalCenter.x = ((int) (globalCenter.x / HITBOX_CLAMP)) * HITBOX_CLAMP; - globalCenter.y = ((int) (globalCenter.y / HITBOX_CLAMP)) * HITBOX_CLAMP; - globalCenter.z = ((int) (globalCenter.z / HITBOX_CLAMP)) * HITBOX_CLAMP; - } - if (definition != null) { - widthRadius = entity.scale.x * definition.width / 2D; - heightRadius = entity.scale.y * definition.height / 2D; - depthRadius = entity.scale.z * definition.width / 2D; - } - } - - /** - * Returns true if the passed-in point is inside this box. - * Note that this returns true for points on the border, to allow use to use in - * in conjunction with hit-scanning code to find out which box got hit-scanned. - */ - public boolean isPointInside(Point3D point, Point3D growthOffset) { - if (growthOffset != null) { - return globalCenter.x - widthRadius - growthOffset.x <= point.x && globalCenter.x + widthRadius + growthOffset.x >= point.x && globalCenter.y - heightRadius - growthOffset.y <= point.y && globalCenter.y + heightRadius + growthOffset.y >= point.y && globalCenter.z - depthRadius - growthOffset.z <= point.z && globalCenter.z + depthRadius + growthOffset.z >= point.z; - } else { - return globalCenter.x - widthRadius <= point.x && globalCenter.x + widthRadius >= point.x && globalCenter.y - heightRadius <= point.y && globalCenter.y + heightRadius >= point.y && globalCenter.z - depthRadius <= point.z && globalCenter.z + depthRadius >= point.z; + public void updateToEntity(AEntityD_Definable entity, Point3D optionalOffset) { + updateToEntity(entity, optionalOffset, null); + } + + public void updateToEntity(AEntityD_Definable entity, Point3D optionalOffset, RotationMatrix optionalRotation) { + if (optionalOffset != null) { + globalCenter.set(optionalOffset); + } else { + globalCenter.set(localCenter); + } + globalCenter.multiply(entity.scale).rotate(entity.orientation).add(entity.position); + orientation.set(entity.orientation); + if (optionalRotation != null) { + orientation.multiply(optionalRotation); + } + if (definition != null && definition.rot != null) { + orientation.multiply(definition.rot); + } + if (groupDef != null && (groupDef.collisionTypes.contains(CollisionType.ENTITY) || groupDef.collisionTypes.contains(CollisionType.VEHICLE))) { + //Need to round box to prevent floating-point errors for player and entity collision. + globalCenter.x = ((int) (globalCenter.x / HITBOX_CLAMP)) * HITBOX_CLAMP; + globalCenter.y = ((int) (globalCenter.y / HITBOX_CLAMP)) * HITBOX_CLAMP; + globalCenter.z = ((int) (globalCenter.z / HITBOX_CLAMP)) * HITBOX_CLAMP; } + if (definition != null) { + widthRadius = entity.scale.x * definition.width / 2D; + heightRadius = entity.scale.y * definition.height / 2D; + depthRadius = entity.scale.z * (definition.length != 0 ? definition.length : definition.width) / 2D; + } + } + + /** + * Returns true if this box should use OBB logic. OBBs are only enabled for + * collision groups whose types are all supported by the OBB implementation. + */ + public boolean isOBB() { + if (groupDef != null && groupDef.isOBB) { + for (CollisionType type : groupDef.collisionTypes) { + if (type != CollisionType.BULLET && type != CollisionType.ATTACK && type != CollisionType.VEHICLE && type != CollisionType.CLICK) { + return false; + } + } + return true; + } + return false; + } + + /** + * Returns the world-axis X radius of this box. For AABBs this is the raw + * width radius; for OBBs it is the projection of the oriented box onto X. + */ + public double getXRadius() { + return isOBB() ? Math.abs(orientation.m00) * widthRadius + Math.abs(orientation.m01) * heightRadius + Math.abs(orientation.m02) * depthRadius : widthRadius; + } + + /** + * Returns the world-axis Y radius of this box. For AABBs this is the raw + * height radius; for OBBs it is the projection of the oriented box onto Y. + */ + public double getYRadius() { + return isOBB() ? Math.abs(orientation.m10) * widthRadius + Math.abs(orientation.m11) * heightRadius + Math.abs(orientation.m12) * depthRadius : heightRadius; + } + + /** + * Returns the world-axis Z radius of this box. For AABBs this is the raw + * depth radius; for OBBs it is the projection of the oriented box onto Z. + */ + public double getZRadius() { + return isOBB() ? Math.abs(orientation.m20) * widthRadius + Math.abs(orientation.m21) * heightRadius + Math.abs(orientation.m22) * depthRadius : depthRadius; + } + + public double getMinX() { + return globalCenter.x - getXRadius(); + } + + public double getMaxX() { + return globalCenter.x + getXRadius(); + } + + public double getMinY() { + return globalCenter.y - getYRadius(); + } + + public double getMaxY() { + return globalCenter.y + getYRadius(); + } + + public double getMinZ() { + return globalCenter.z - getZRadius(); + } + + public double getMaxZ() { + return globalCenter.z + getZRadius(); + } + + /** + * Returns true if the passed-in point is inside this box. + * Note that this returns true for points on the border, to allow use to use in + * in conjunction with hit-scanning code to find out which box got hit-scanned. + */ + public boolean isPointInside(Point3D point, Point3D growthOffset) { + if (isOBB()) { + Point3D localPoint = point.copy().subtract(globalCenter).reOrigin(orientation); + double growthX = growthOffset != null ? growthOffset.x : 0; + double growthY = growthOffset != null ? growthOffset.y : 0; + double growthZ = growthOffset != null ? growthOffset.z : 0; + return localPoint.x >= -widthRadius - growthX && localPoint.x <= widthRadius + growthX && localPoint.y >= -heightRadius - growthY && localPoint.y <= heightRadius + growthY && localPoint.z >= -depthRadius - growthZ && localPoint.z <= depthRadius + growthZ; + } + if (growthOffset != null) { + return globalCenter.x - widthRadius - growthOffset.x <= point.x && globalCenter.x + widthRadius + growthOffset.x >= point.x && globalCenter.y - heightRadius - growthOffset.y <= point.y && globalCenter.y + heightRadius + growthOffset.y >= point.y && globalCenter.z - depthRadius - growthOffset.z <= point.z && globalCenter.z + depthRadius + growthOffset.z >= point.z; + } else { + return globalCenter.x - widthRadius <= point.x && globalCenter.x + widthRadius >= point.x && globalCenter.y - heightRadius <= point.y && globalCenter.y + heightRadius >= point.y && globalCenter.z - depthRadius <= point.z && globalCenter.z + depthRadius >= point.z; + } } /** - * Returns true if the passed-in point is inside this box in the XZ plane, and is below this box. - */ - public boolean isPointInsideAndBelow(Point3D point) { - return globalCenter.x - widthRadius <= point.x && globalCenter.x + widthRadius >= point.x && globalCenter.y + heightRadius > point.y && globalCenter.z - depthRadius <= point.z && globalCenter.z + depthRadius >= point.z; - } - - /** - * Returns true if the passed-in box intersects this box. - */ - public boolean intersects(BoundingBox box) { - return globalCenter.x - widthRadius < box.globalCenter.x + box.widthRadius && globalCenter.x + widthRadius > box.globalCenter.x - box.widthRadius && globalCenter.y - heightRadius < box.globalCenter.y + box.heightRadius && globalCenter.y + heightRadius > box.globalCenter.y - box.heightRadius && globalCenter.z - depthRadius < box.globalCenter.z + box.depthRadius && globalCenter.z + depthRadius > box.globalCenter.z - box.depthRadius; - } + * Returns true if the passed-in point is inside this box in the XZ plane, and is below this box. + */ + public boolean isPointInsideAndBelow(Point3D point) { + if (isOBB()) { + Point3D localPoint = point.copy().subtract(globalCenter).reOrigin(orientation); + return localPoint.x >= -widthRadius && localPoint.x <= widthRadius && localPoint.y <= heightRadius && localPoint.z >= -depthRadius && localPoint.z <= depthRadius; + } + return globalCenter.x - widthRadius <= point.x && globalCenter.x + widthRadius >= point.x && globalCenter.y + heightRadius > point.y && globalCenter.z - depthRadius <= point.z && globalCenter.z + depthRadius >= point.z; + } + + /** + * Returns true if the passed-in box intersects this box. + */ + public boolean intersects(BoundingBox box) { + if (isOBB() || box.isOBB()) { + return intersectsOBB(box); + } + return globalCenter.x - widthRadius < box.globalCenter.x + box.widthRadius && globalCenter.x + widthRadius > box.globalCenter.x - box.widthRadius && globalCenter.y - heightRadius < box.globalCenter.y + box.heightRadius && globalCenter.y + heightRadius > box.globalCenter.y - box.heightRadius && globalCenter.z - depthRadius < box.globalCenter.z + box.depthRadius && globalCenter.z + depthRadius > box.globalCenter.z - box.depthRadius; + } + + /** + * Returns true if this box intersects the passed-in world-axis bounds. + */ + public boolean intersects(double otherMinX, double otherMinY, double otherMinZ, double otherMaxX, double otherMaxY, double otherMaxZ) { + if (isOBB()) { + return intersects(new BoundingBox(new Point3D((otherMinX + otherMaxX) / 2D, (otherMinY + otherMaxY) / 2D, (otherMinZ + otherMaxZ) / 2D), (otherMaxX - otherMinX) / 2D, (otherMaxY - otherMinY) / 2D, (otherMaxZ - otherMinZ) / 2D)); + } + return otherMaxX > globalCenter.x - widthRadius && otherMinX < globalCenter.x + widthRadius && otherMaxY > globalCenter.y - heightRadius && otherMinY < globalCenter.y + heightRadius && otherMaxZ > globalCenter.z - depthRadius && otherMinZ < globalCenter.z + depthRadius; + } + + private boolean intersectsOBB(BoundingBox box) { + double[] thisRadii = { widthRadius, heightRadius, depthRadius }; + double[] otherRadii = { box.widthRadius, box.heightRadius, box.depthRadius }; + double[][] rotation = new double[3][3]; + double[][] absRotation = new double[3][3]; + boolean thisOBB = isOBB(); + boolean otherOBB = box.isOBB(); + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + rotation[i][j] = getAxisDot(this, i, thisOBB, box, j, otherOBB); + absRotation[i][j] = Math.abs(rotation[i][j]) + OBB_EPSILON; + } + } + + Point3D centerDelta = box.globalCenter.copy().subtract(globalCenter); + double[] translation = { + getAxisDot(centerDelta, this, 0, thisOBB), + getAxisDot(centerDelta, this, 1, thisOBB), + getAxisDot(centerDelta, this, 2, thisOBB) + }; + + for (int i = 0; i < 3; ++i) { + double otherRadius = otherRadii[0] * absRotation[i][0] + otherRadii[1] * absRotation[i][1] + otherRadii[2] * absRotation[i][2]; + if (Math.abs(translation[i]) > thisRadii[i] + otherRadius) { + return false; + } + } + + for (int j = 0; j < 3; ++j) { + double thisRadius = thisRadii[0] * absRotation[0][j] + thisRadii[1] * absRotation[1][j] + thisRadii[2] * absRotation[2][j]; + double distance = Math.abs(translation[0] * rotation[0][j] + translation[1] * rotation[1][j] + translation[2] * rotation[2][j]); + if (distance > thisRadius + otherRadii[j]) { + return false; + } + } + + for (int i = 0; i < 3; ++i) { + int i1 = (i + 1) % 3; + int i2 = (i + 2) % 3; + for (int j = 0; j < 3; ++j) { + int j1 = (j + 1) % 3; + int j2 = (j + 2) % 3; + double thisRadius = thisRadii[i1] * absRotation[i2][j] + thisRadii[i2] * absRotation[i1][j]; + double otherRadius = otherRadii[j1] * absRotation[i][j2] + otherRadii[j2] * absRotation[i][j1]; + double distance = Math.abs(translation[i2] * rotation[i1][j] - translation[i1] * rotation[i2][j]); + if (distance > thisRadius + otherRadius) { + return false; + } + } + } + return true; + } + + private static double getAxisDot(BoundingBox firstBox, int firstAxis, boolean firstOBB, BoundingBox secondBox, int secondAxis, boolean secondOBB) { + return getAxisComponent(firstBox, firstAxis, firstOBB, 0) * getAxisComponent(secondBox, secondAxis, secondOBB, 0) + getAxisComponent(firstBox, firstAxis, firstOBB, 1) * getAxisComponent(secondBox, secondAxis, secondOBB, 1) + getAxisComponent(firstBox, firstAxis, firstOBB, 2) * getAxisComponent(secondBox, secondAxis, secondOBB, 2); + } + + private static double getAxisDot(Point3D point, BoundingBox box, int axis, boolean boxOBB) { + return point.x * getAxisComponent(box, axis, boxOBB, 0) + point.y * getAxisComponent(box, axis, boxOBB, 1) + point.z * getAxisComponent(box, axis, boxOBB, 2); + } + + private static double getAxisComponent(BoundingBox box, int axis, boolean boxOBB, int component) { + if (!boxOBB) { + return axis == component ? 1D : 0D; + } + switch (axis) { + case 0: + return component == 0 ? box.orientation.m00 : component == 1 ? box.orientation.m10 : box.orientation.m20; + case 1: + return component == 0 ? box.orientation.m01 : component == 1 ? box.orientation.m11 : box.orientation.m21; + default: + return component == 0 ? box.orientation.m02 : component == 1 ? box.orientation.m12 : box.orientation.m22; + } + } /** * Returns true if the passed-in point intersects this box in the YZ-plane. @@ -240,10 +417,14 @@ private Point3D getZPlaneCollision(Point3D start, Point3D end, double zPoint) { * If so, then a new point is returned on the first point of intersection (outer bounds). If the * line created by the two points does not intersect this box, null is returned. */ - public BoundingBoxHitResult getIntersection(Point3D start, Point3D end) { - //First check minX. - Point3D intersection = getXPlaneCollision(start, end, globalCenter.x - widthRadius); - Axis hitSide = Axis.WEST; + public BoundingBoxHitResult getIntersection(Point3D start, Point3D end) { + if (isOBB()) { + return getOBBIntersection(start, end); + } + + //First check minX. + Point3D intersection = getXPlaneCollision(start, end, globalCenter.x - widthRadius); + Axis hitSide = Axis.WEST; //Now get maxX. //If minX is null, or if maxX is not null, and is closer to the start point than minX, it's our new intersection. @@ -280,8 +461,82 @@ public BoundingBoxHitResult getIntersection(Point3D start, Point3D end) { intersection = secondIntersection; hitSide = Axis.SOUTH; } - return intersection != null ? new BoundingBoxHitResult(this, intersection, hitSide) : null; - } + return intersection != null ? new BoundingBoxHitResult(this, intersection, hitSide) : null; + } + + private BoundingBoxHitResult getOBBIntersection(Point3D start, Point3D end) { + Point3D localStart = start.copy().subtract(globalCenter).reOrigin(orientation); + Point3D localEnd = end.copy().subtract(globalCenter).reOrigin(orientation); + Point3D localDelta = localEnd.copy().subtract(localStart); + double[] startValues = { localStart.x, localStart.y, localStart.z }; + double[] deltaValues = { localDelta.x, localDelta.y, localDelta.z }; + double[] radii = { widthRadius, heightRadius, depthRadius }; + double minFactor = Double.NEGATIVE_INFINITY; + double maxFactor = Double.POSITIVE_INFINITY; + Axis minSide = Axis.NONE; + Axis maxSide = Axis.NONE; + Point3D minNormal = new Point3D(); + Point3D maxNormal = new Point3D(); + + for (int axis = 0; axis < 3; ++axis) { + if (Math.abs(deltaValues[axis]) < OBB_EPSILON) { + if (startValues[axis] < -radii[axis] || startValues[axis] > radii[axis]) { + return null; + } + } else { + double firstFactor = (-radii[axis] - startValues[axis]) / deltaValues[axis]; + double secondFactor = (radii[axis] - startValues[axis]) / deltaValues[axis]; + Axis firstSide = getOBBSide(axis, false); + Axis secondSide = getOBBSide(axis, true); + Point3D firstNormal = getOBBNormal(axis, false); + Point3D secondNormal = getOBBNormal(axis, true); + if (firstFactor > secondFactor) { + double priorFactor = firstFactor; + Axis priorSide = firstSide; + Point3D priorNormal = firstNormal; + firstFactor = secondFactor; + firstSide = secondSide; + firstNormal = secondNormal; + secondFactor = priorFactor; + secondSide = priorSide; + secondNormal = priorNormal; + } + if (firstFactor > minFactor) { + minFactor = firstFactor; + minSide = firstSide; + minNormal.set(firstNormal); + } + if (secondFactor < maxFactor) { + maxFactor = secondFactor; + maxSide = secondSide; + maxNormal.set(secondNormal); + } + if (minFactor > maxFactor) { + return null; + } + } + } + + if (maxFactor < 0 || minFactor > 1) { + return null; + } + double hitFactor = minFactor >= 0 ? minFactor : maxFactor; + if (hitFactor < 0 || hitFactor > 1) { + return null; + } + Point3D intersection = end.copy().subtract(start).scale(hitFactor).add(start); + Axis hitSide = minFactor >= 0 ? minSide : maxSide; + Point3D hitNormal = minFactor >= 0 ? minNormal : maxNormal; + return new BoundingBoxHitResult(this, intersection, hitNormal, hitSide); + } + + private Axis getOBBSide(int axis, boolean positive) { + return Axis.getFromVector(getOBBNormal(axis, positive)); + } + + private Point3D getOBBNormal(int axis, boolean positive) { + return new Point3D(axis == 0 ? (positive ? 1 : -1) : 0, axis == 1 ? (positive ? 1 : -1) : 0, axis == 2 ? (positive ? 1 : -1) : 0).rotate(orientation); + } /** * Renders this bounding box as a wireframe model. @@ -316,12 +571,15 @@ public void renderWireframe(AEntityC_Renderable entity, TransformationMatrix tra helperPoint.add(offset); } else { helperPoint.subtract(entity.position); - } - wireframeRenderable.transform.applyTranslation(helperPoint); - if (color != null) { - //Override default color with set color. - wireframeRenderable.setColor(color); - } + } + wireframeRenderable.transform.applyTranslation(helperPoint); + if (isOBB()) { + wireframeRenderable.transform.applyRotation(orientation); + } + if (color != null) { + //Override default color with set color. + wireframeRenderable.setColor(color); + } wireframeRenderable.setBoxBounds(this, true); wireframeRenderable.render(); } diff --git a/mccore/src/main/java/minecrafttransportsimulator/baseclasses/BoundingBoxHitResult.java b/mccore/src/main/java/minecrafttransportsimulator/baseclasses/BoundingBoxHitResult.java index 72dd5f4e1..a1efe8551 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/baseclasses/BoundingBoxHitResult.java +++ b/mccore/src/main/java/minecrafttransportsimulator/baseclasses/BoundingBoxHitResult.java @@ -6,13 +6,19 @@ * Class for hitting on bounding boxes. */ public class BoundingBoxHitResult { - public final BoundingBox box; - public final Point3D position; - public final Axis side; - - public BoundingBoxHitResult(BoundingBox box, Point3D hitPosition, Axis side) { - this.box = box; - this.position = hitPosition; - this.side = side; - } -} \ No newline at end of file + public final BoundingBox box; + public final Point3D position; + public final Point3D normal; + public final Axis side; + + public BoundingBoxHitResult(BoundingBox box, Point3D hitPosition, Axis side) { + this(box, hitPosition, new Point3D(side.xOffset, side.yOffset, side.zOffset), side); + } + + public BoundingBoxHitResult(BoundingBox box, Point3D hitPosition, Point3D normal, Axis side) { + this.box = box; + this.position = hitPosition; + this.normal = normal; + this.side = side; + } +} diff --git a/mccore/src/main/java/minecrafttransportsimulator/baseclasses/VehicleGroundDeviceBox.java b/mccore/src/main/java/minecrafttransportsimulator/baseclasses/VehicleGroundDeviceBox.java index 5ddec699c..b6d961b1c 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/baseclasses/VehicleGroundDeviceBox.java +++ b/mccore/src/main/java/minecrafttransportsimulator/baseclasses/VehicleGroundDeviceBox.java @@ -434,13 +434,13 @@ private boolean checkEntityCollisions(Point3D collisionMotion) { for (BoundingBox box : otherVehicle.allCollisionBoxes) { if (box.collisionTypes.contains(CollisionType.VEHICLE) && box.intersects(solidBox)) { if (collisionMotion.y > 0) { - boxCollisionDepth = solidBox.globalCenter.y + solidBox.heightRadius - (box.globalCenter.y - box.heightRadius); + boxCollisionDepth = solidBox.globalCenter.y + solidBox.heightRadius - (box.globalCenter.y - box.getYRadius()); if (boxCollisionDepth > solidBox.currentCollisionDepth.y) { solidBox.currentCollisionDepth.y = boxCollisionDepth; collidingBox = box; } } else { - boxCollisionDepth = solidBox.globalCenter.y - solidBox.heightRadius - (box.globalCenter.y + box.heightRadius); + boxCollisionDepth = solidBox.globalCenter.y - solidBox.heightRadius - (box.globalCenter.y + box.getYRadius()); if (boxCollisionDepth < solidBox.currentCollisionDepth.y) { solidBox.currentCollisionDepth.y = boxCollisionDepth; collidingBox = box; diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityE_Interactable.java b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityE_Interactable.java index 0db5bf8b2..f9c735c7f 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityE_Interactable.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityE_Interactable.java @@ -251,7 +251,7 @@ protected void updateCollisionBoxes(boolean requiresDeltaUpdates) { if (requiresDeltaUpdates) { for (BoundingBox box : boxes) { box.globalCenter.set(box.localCenter).transform(switchBox.netMatrix); - box.updateToEntity(this, box.globalCenter); + box.updateToEntity(this, box.globalCenter, switchBox.rotation); } } } else { @@ -310,9 +310,9 @@ protected void updateEncompassingBox() { encompassingBox.heightRadius = 0; encompassingBox.depthRadius = 0; for (BoundingBox box : collisionBoxes) { - encompassingBox.widthRadius = (float) Math.max(encompassingBox.widthRadius, Math.abs(box.globalCenter.x - position.x) + box.widthRadius); - encompassingBox.heightRadius = (float) Math.max(encompassingBox.heightRadius, Math.abs(box.globalCenter.y - position.y) + box.heightRadius); - encompassingBox.depthRadius = (float) Math.max(encompassingBox.depthRadius, Math.abs(box.globalCenter.z - position.z) + box.depthRadius); + encompassingBox.widthRadius = (float) Math.max(encompassingBox.widthRadius, Math.abs(box.globalCenter.x - position.x) + box.getXRadius()); + encompassingBox.heightRadius = (float) Math.max(encompassingBox.heightRadius, Math.abs(box.globalCenter.y - position.y) + box.getYRadius()); + encompassingBox.depthRadius = (float) Math.max(encompassingBox.depthRadius, Math.abs(box.globalCenter.z - position.z) + box.getZRadius()); } encompassingBox.updateToEntity(this, null); } diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java index 890b10653..2e57d67e8 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java @@ -358,9 +358,10 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, hitOperationalHitbox = true; if (bullet != null) { double armorThickness = hitEntry.box.definition != null ? (bullet.definition.bullet.isHeat && hitEntry.box.groupDef.heatArmorThickness != 0 ? hitEntry.box.groupDef.heatArmorThickness : hitEntry.box.groupDef.armorThickness) : 0; + double effectiveArmorThickness = getEffectiveArmorThickness(hitEntry, bullet, armorThickness); double penetrationPotential = bullet.definition.bullet.isHeat ? bullet.definition.bullet.armorPenetration : (bullet.definition.bullet.armorPenetration * bullet.velocity / bullet.initialVelocity); - bullet.armorPenetrated += armorThickness; - bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness); + bullet.armorPenetrated += effectiveArmorThickness; + bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness + " EFFECTIVE: " + (int) effectiveArmorThickness); if (bullet.armorPenetrated > penetrationPotential) { //Bullet hit too much armor. @@ -425,6 +426,19 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, return null; } + private double getEffectiveArmorThickness(BoundingBoxHitResult hitEntry, EntityBullet bullet, double armorThickness) { + if (armorThickness == 0) { + return 0; + } + Point3D bulletDirection = bullet.motion.copy().normalize(); + Point3D hitNormal = hitEntry.normal.copy().normalize(); + if (bulletDirection.isZero() || hitNormal.isZero()) { + return armorThickness; + } + double armorImpactFactor = Math.max(Math.abs(hitNormal.dotProduct(bulletDirection, true)), 0.001D); + return armorThickness / armorImpactFactor; + } + @Override public void remove() { super.remove(); diff --git a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionBox.java b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionBox.java index 53e69157f..993e74fca 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionBox.java +++ b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionBox.java @@ -1,6 +1,7 @@ package minecrafttransportsimulator.jsondefs; -import minecrafttransportsimulator.baseclasses.Point3D; +import minecrafttransportsimulator.baseclasses.Point3D; +import minecrafttransportsimulator.baseclasses.RotationMatrix; import minecrafttransportsimulator.jsondefs.JSONAction.ActionType; import minecrafttransportsimulator.packloading.JSONParser.JSONDescription; import minecrafttransportsimulator.packloading.JSONParser.JSONRequired; @@ -10,14 +11,20 @@ public class JSONCollisionBox { @JSONDescription("n entry of x, y, and z coordinates that define the center point of where this collision box is relative to the center of the object.") public Point3D pos; - @JSONDescription("The width of this collision box, in meters. Note that since the pos parameter is the center of the box the box will actually extend � the width in the X and Z direction.") - public float width; - - @JSONDescription("Same as width, just for the Y direction.") - public float height; - - @JSONDescription("If true, the collision box will behave like a ground device set to float. Note that if you make a boat that uses only these boxes, you'll need one for every corner like you would wheels on a car. Failing to do so will result in your boat doing a Titanic, just without an iceberg.") - public boolean collidesWithLiquids; + @JSONDescription("The width of this collision box, in meters. Note that since the pos parameter is the center of the box the box will actually extend � the width in the X and Z direction.") + public float width; + + @JSONDescription("The length of this collision box, in meters. If omitted, width will be used for the Z direction.") + public float length; + + @JSONDescription("Same as width, just for the Y direction.") + public float height; + + @JSONDescription("The rotation of this collision box. Only used if the owning collision group has isOBB set to true.") + public RotationMatrix rot; + + @JSONDescription("If true, the collision box will behave like a ground device set to float. Note that if you make a boat that uses only these boxes, you'll need one for every corner like you would wheels on a car. Failing to do so will result in your boat doing a Titanic, just without an iceberg.") + public boolean collidesWithLiquids; @JSONDescription("The action to perform when clicking this hitbox, if any.") public JSONAction action; diff --git a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java index 7b93fdd5d..3539d30dd 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java +++ b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java @@ -1,10 +1,10 @@ package minecrafttransportsimulator.jsondefs; -import java.util.List; -import java.util.Set; - -import minecrafttransportsimulator.packloading.JSONParser.JSONDescription; -import minecrafttransportsimulator.packloading.JSONParser.JSONRequired; +import java.util.List; +import java.util.Set; + +import minecrafttransportsimulator.packloading.JSONParser.JSONDescription; +import minecrafttransportsimulator.packloading.JSONParser.JSONRequired; public class JSONCollisionGroup { @@ -29,12 +29,15 @@ public class JSONCollisionGroup { @JSONDescription("How much health this collision group has. When the health reaches 0, this collision group will be disabled. Useful for armor that can only take so many hits, or parts of models that you want to break off when hit. If this value is 0, then the collision group will always be present, and damage inflicted will be charged to the entity rather than this collision group.") public int health; - @JSONDescription("If this is set, then this collision group will first use the animations for this object from the rendering section instead of the animations in this one. If the specified object has applyAfter on it itself, then the animations will be gotten recursively until an applyAfter is not found.") - public String applyAfter; - - @JSONRequired - @JSONDescription("The types of collision for this group.") - public Set collisionTypes; + @JSONDescription("If this is set, then this collision group will first use the animations for this object from the rendering section instead of the animations in this one. If the specified object has applyAfter on it itself, then the animations will be gotten recursively until an applyAfter is not found.") + public String applyAfter; + + @JSONDescription("If true, this collision group will use oriented bounding box logic. This only applies to BULLET, ATTACK, VEHICLE, and CLICK collision types.") + public boolean isOBB; + + @JSONRequired + @JSONDescription("The types of collision for this group.") + public Set collisionTypes; @JSONRequired @JSONDescription("A listing of collisions for this group.") diff --git a/mcinterfaceforge1122/src/main/java/mcinterface1122/WrapperAABBCollective.java b/mcinterfaceforge1122/src/main/java/mcinterface1122/WrapperAABBCollective.java index b3006fc71..89cb4d139 100644 --- a/mcinterfaceforge1122/src/main/java/mcinterface1122/WrapperAABBCollective.java +++ b/mcinterfaceforge1122/src/main/java/mcinterface1122/WrapperAABBCollective.java @@ -128,7 +128,7 @@ public boolean intersects(double otherMinX, double otherMinY, double otherMinZ, //CHeck super first, as that's the encompassing box. if (super.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { for (BoundingBox testBox : getBoxes()) { - if (otherMaxX > testBox.globalCenter.x - testBox.widthRadius && otherMinX < testBox.globalCenter.x + testBox.widthRadius && otherMaxY > testBox.globalCenter.y - testBox.heightRadius && otherMinY < testBox.globalCenter.y + testBox.heightRadius && otherMaxZ > testBox.globalCenter.z - testBox.depthRadius && otherMinZ < testBox.globalCenter.z + testBox.depthRadius) { + if (testBox.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { return true; } } diff --git a/mcinterfaceforge1122/src/main/java/mcinterface1122/WrapperWorld.java b/mcinterfaceforge1122/src/main/java/mcinterface1122/WrapperWorld.java index 3b29d89a5..9ee68dba0 100644 --- a/mcinterfaceforge1122/src/main/java/mcinterface1122/WrapperWorld.java +++ b/mcinterfaceforge1122/src/main/java/mcinterface1122/WrapperWorld.java @@ -1001,7 +1001,7 @@ public void spawnExplosion(Point3D location, double strength, boolean flames, bo * Helper method to convert a BoundingBox to an AxisAlignedBB. */ public static AxisAlignedBB convert(BoundingBox box) { - return new AxisAlignedBB(box.globalCenter.x - box.widthRadius, box.globalCenter.y - box.heightRadius, box.globalCenter.z - box.depthRadius, box.globalCenter.x + box.widthRadius, box.globalCenter.y + box.heightRadius, box.globalCenter.z + box.depthRadius); + return new AxisAlignedBB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()); } /** @@ -1010,7 +1010,7 @@ public static AxisAlignedBB convert(BoundingBox box) { * creating two AABBs (the conversion and the offset box). */ public static AxisAlignedBB convertWithOffset(BoundingBox box, double x, double y, double z) { - return new AxisAlignedBB(x + box.globalCenter.x - box.widthRadius, y + box.globalCenter.y - box.heightRadius, z + box.globalCenter.z - box.depthRadius, x + box.globalCenter.x + box.widthRadius, y + box.globalCenter.y + box.heightRadius, z + box.globalCenter.z + box.depthRadius); + return new AxisAlignedBB(x + box.getMinX(), y + box.getMinY(), z + box.getMinZ(), x + box.getMaxX(), y + box.getMaxY(), z + box.getMaxZ()); } /** diff --git a/mcinterfaceforge1165/src/main/java/mcinterface1165/WrapperAABBCollective.java b/mcinterfaceforge1165/src/main/java/mcinterface1165/WrapperAABBCollective.java index e8b3d04f7..f105ab9b8 100644 --- a/mcinterfaceforge1165/src/main/java/mcinterface1165/WrapperAABBCollective.java +++ b/mcinterfaceforge1165/src/main/java/mcinterface1165/WrapperAABBCollective.java @@ -59,7 +59,7 @@ public boolean intersects(double otherMinX, double otherMinY, double otherMinZ, //CHeck super first, as that's the encompassing box. if (super.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { for (BoundingBox testBox : getBoxes()) { - if (otherMaxX > testBox.globalCenter.x - testBox.widthRadius && otherMinX < testBox.globalCenter.x + testBox.widthRadius && otherMaxY > testBox.globalCenter.y - testBox.heightRadius && otherMinY < testBox.globalCenter.y + testBox.heightRadius && otherMaxZ > testBox.globalCenter.z - testBox.depthRadius && otherMinZ < testBox.globalCenter.z + testBox.depthRadius) { + if (testBox.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { return true; } } diff --git a/mcinterfaceforge1165/src/main/java/mcinterface1165/WrapperWorld.java b/mcinterfaceforge1165/src/main/java/mcinterface1165/WrapperWorld.java index 3e144efe6..4559a48d5 100644 --- a/mcinterfaceforge1165/src/main/java/mcinterface1165/WrapperWorld.java +++ b/mcinterfaceforge1165/src/main/java/mcinterface1165/WrapperWorld.java @@ -1021,7 +1021,7 @@ public void spawnExplosion(Point3D location, double strength, boolean flames, bo * Helper method to convert a BoundingBox to an AxisAlignedBB. */ public static AxisAlignedBB convert(BoundingBox box) { - return new AxisAlignedBB(box.globalCenter.x - box.widthRadius, box.globalCenter.y - box.heightRadius, box.globalCenter.z - box.depthRadius, box.globalCenter.x + box.widthRadius, box.globalCenter.y + box.heightRadius, box.globalCenter.z + box.depthRadius); + return new AxisAlignedBB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()); } /** @@ -1030,7 +1030,7 @@ public static AxisAlignedBB convert(BoundingBox box) { * creating two AABBs (the conversion and the offset box). */ public static AxisAlignedBB convertWithOffset(BoundingBox box, double x, double y, double z) { - return new AxisAlignedBB(x + box.globalCenter.x - box.widthRadius, y + box.globalCenter.y - box.heightRadius, z + box.globalCenter.z - box.depthRadius, x + box.globalCenter.x + box.widthRadius, y + box.globalCenter.y + box.heightRadius, z + box.globalCenter.z + box.depthRadius); + return new AxisAlignedBB(x + box.getMinX(), y + box.getMinY(), z + box.getMinZ(), x + box.getMaxX(), y + box.getMaxY(), z + box.getMaxZ()); } /** diff --git a/mcinterfaceforge1182/src/main/java/mcinterface1182/WrapperAABBCollective.java b/mcinterfaceforge1182/src/main/java/mcinterface1182/WrapperAABBCollective.java index 24144c934..8e7254a9f 100644 --- a/mcinterfaceforge1182/src/main/java/mcinterface1182/WrapperAABBCollective.java +++ b/mcinterfaceforge1182/src/main/java/mcinterface1182/WrapperAABBCollective.java @@ -59,7 +59,7 @@ public boolean intersects(double otherMinX, double otherMinY, double otherMinZ, //CHeck super first, as that's the encompassing box. if (super.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { for (BoundingBox testBox : getBoxes()) { - if (otherMaxX > testBox.globalCenter.x - testBox.widthRadius && otherMinX < testBox.globalCenter.x + testBox.widthRadius && otherMaxY > testBox.globalCenter.y - testBox.heightRadius && otherMinY < testBox.globalCenter.y + testBox.heightRadius && otherMaxZ > testBox.globalCenter.z - testBox.depthRadius && otherMinZ < testBox.globalCenter.z + testBox.depthRadius) { + if (testBox.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { return true; } } diff --git a/mcinterfaceforge1182/src/main/java/mcinterface1182/WrapperWorld.java b/mcinterfaceforge1182/src/main/java/mcinterface1182/WrapperWorld.java index 3a499310f..14f6a0790 100644 --- a/mcinterfaceforge1182/src/main/java/mcinterface1182/WrapperWorld.java +++ b/mcinterfaceforge1182/src/main/java/mcinterface1182/WrapperWorld.java @@ -1002,7 +1002,7 @@ public void spawnExplosion(Point3D location, double strength, boolean flames, bo * Helper method to convert a BoundingBox to an AABB. */ public static AABB convert(BoundingBox box) { - return new AABB(box.globalCenter.x - box.widthRadius, box.globalCenter.y - box.heightRadius, box.globalCenter.z - box.depthRadius, box.globalCenter.x + box.widthRadius, box.globalCenter.y + box.heightRadius, box.globalCenter.z + box.depthRadius); + return new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()); } /** @@ -1011,7 +1011,7 @@ public static AABB convert(BoundingBox box) { * creating two AABBs (the conversion and the offset box). */ public static AABB convertWithOffset(BoundingBox box, double x, double y, double z) { - return new AABB(x + box.globalCenter.x - box.widthRadius, y + box.globalCenter.y - box.heightRadius, z + box.globalCenter.z - box.depthRadius, x + box.globalCenter.x + box.widthRadius, y + box.globalCenter.y + box.heightRadius, z + box.globalCenter.z + box.depthRadius); + return new AABB(x + box.getMinX(), y + box.getMinY(), z + box.getMinZ(), x + box.getMaxX(), y + box.getMaxY(), z + box.getMaxZ()); } /** diff --git a/mcinterfaceforge1192/src/main/java/mcinterface1192/WrapperAABBCollective.java b/mcinterfaceforge1192/src/main/java/mcinterface1192/WrapperAABBCollective.java index 11c648f8f..0a0f2f8a2 100644 --- a/mcinterfaceforge1192/src/main/java/mcinterface1192/WrapperAABBCollective.java +++ b/mcinterfaceforge1192/src/main/java/mcinterface1192/WrapperAABBCollective.java @@ -59,7 +59,7 @@ public boolean intersects(double otherMinX, double otherMinY, double otherMinZ, //CHeck super first, as that's the encompassing box. if (super.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { for (BoundingBox testBox : getBoxes()) { - if (otherMaxX > testBox.globalCenter.x - testBox.widthRadius && otherMinX < testBox.globalCenter.x + testBox.widthRadius && otherMaxY > testBox.globalCenter.y - testBox.heightRadius && otherMinY < testBox.globalCenter.y + testBox.heightRadius && otherMaxZ > testBox.globalCenter.z - testBox.depthRadius && otherMinZ < testBox.globalCenter.z + testBox.depthRadius) { + if (testBox.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { return true; } } diff --git a/mcinterfaceforge1192/src/main/java/mcinterface1192/WrapperWorld.java b/mcinterfaceforge1192/src/main/java/mcinterface1192/WrapperWorld.java index 93514ba30..7aeeec919 100644 --- a/mcinterfaceforge1192/src/main/java/mcinterface1192/WrapperWorld.java +++ b/mcinterfaceforge1192/src/main/java/mcinterface1192/WrapperWorld.java @@ -1003,7 +1003,7 @@ public void spawnExplosion(Point3D location, double strength, boolean flames, bo * Helper method to convert a BoundingBox to an AABB. */ public static AABB convert(BoundingBox box) { - return new AABB(box.globalCenter.x - box.widthRadius, box.globalCenter.y - box.heightRadius, box.globalCenter.z - box.depthRadius, box.globalCenter.x + box.widthRadius, box.globalCenter.y + box.heightRadius, box.globalCenter.z + box.depthRadius); + return new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()); } /** @@ -1012,7 +1012,7 @@ public static AABB convert(BoundingBox box) { * creating two AABBs (the conversion and the offset box). */ public static AABB convertWithOffset(BoundingBox box, double x, double y, double z) { - return new AABB(x + box.globalCenter.x - box.widthRadius, y + box.globalCenter.y - box.heightRadius, z + box.globalCenter.z - box.depthRadius, x + box.globalCenter.x + box.widthRadius, y + box.globalCenter.y + box.heightRadius, z + box.globalCenter.z + box.depthRadius); + return new AABB(x + box.getMinX(), y + box.getMinY(), z + box.getMinZ(), x + box.getMaxX(), y + box.getMaxY(), z + box.getMaxZ()); } /** diff --git a/mcinterfaceforge1201/src/main/java/mcinterface1201/WrapperAABBCollective.java b/mcinterfaceforge1201/src/main/java/mcinterface1201/WrapperAABBCollective.java index d6376bf81..9db67706e 100644 --- a/mcinterfaceforge1201/src/main/java/mcinterface1201/WrapperAABBCollective.java +++ b/mcinterfaceforge1201/src/main/java/mcinterface1201/WrapperAABBCollective.java @@ -59,7 +59,7 @@ public boolean intersects(double otherMinX, double otherMinY, double otherMinZ, //CHeck super first, as that's the encompassing box. if (super.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { for (BoundingBox testBox : getBoxes()) { - if (otherMaxX > testBox.globalCenter.x - testBox.widthRadius && otherMinX < testBox.globalCenter.x + testBox.widthRadius && otherMaxY > testBox.globalCenter.y - testBox.heightRadius && otherMinY < testBox.globalCenter.y + testBox.heightRadius && otherMaxZ > testBox.globalCenter.z - testBox.depthRadius && otherMinZ < testBox.globalCenter.z + testBox.depthRadius) { + if (testBox.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { return true; } } diff --git a/mcinterfaceforge1201/src/main/java/mcinterface1201/WrapperWorld.java b/mcinterfaceforge1201/src/main/java/mcinterface1201/WrapperWorld.java index 24e68313b..c99bde65c 100644 --- a/mcinterfaceforge1201/src/main/java/mcinterface1201/WrapperWorld.java +++ b/mcinterfaceforge1201/src/main/java/mcinterface1201/WrapperWorld.java @@ -1026,7 +1026,7 @@ public void spawnExplosion(Point3D location, double strength, boolean flames, bo * Helper method to convert a BoundingBox to an AABB. */ public static AABB convert(BoundingBox box) { - return new AABB(box.globalCenter.x - box.widthRadius, box.globalCenter.y - box.heightRadius, box.globalCenter.z - box.depthRadius, box.globalCenter.x + box.widthRadius, box.globalCenter.y + box.heightRadius, box.globalCenter.z + box.depthRadius); + return new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()); } /** @@ -1035,7 +1035,7 @@ public static AABB convert(BoundingBox box) { * creating two AABBs (the conversion and the offset box). */ public static AABB convertWithOffset(BoundingBox box, double x, double y, double z) { - return new AABB(x + box.globalCenter.x - box.widthRadius, y + box.globalCenter.y - box.heightRadius, z + box.globalCenter.z - box.depthRadius, x + box.globalCenter.x + box.widthRadius, y + box.globalCenter.y + box.heightRadius, z + box.globalCenter.z + box.depthRadius); + return new AABB(x + box.getMinX(), y + box.getMinY(), z + box.getMinZ(), x + box.getMaxX(), y + box.getMaxY(), z + box.getMaxZ()); } /** diff --git a/mcinterfaceneoforge1211/src/main/java/mcinterface1211/WrapperAABBCollective.java b/mcinterfaceneoforge1211/src/main/java/mcinterface1211/WrapperAABBCollective.java index 1deca1569..fa402e9ae 100644 --- a/mcinterfaceneoforge1211/src/main/java/mcinterface1211/WrapperAABBCollective.java +++ b/mcinterfaceneoforge1211/src/main/java/mcinterface1211/WrapperAABBCollective.java @@ -59,9 +59,9 @@ public boolean intersects(double otherMinX, double otherMinY, double otherMinZ, //CHeck super first, as that's the encompassing box. if (super.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { for (BoundingBox testBox : getBoxes()) { - if (otherMaxX > testBox.globalCenter.x - testBox.widthRadius && otherMinX < testBox.globalCenter.x + testBox.widthRadius && otherMaxY > testBox.globalCenter.y - testBox.heightRadius && otherMinY < testBox.globalCenter.y + testBox.heightRadius && otherMaxZ > testBox.globalCenter.z - testBox.depthRadius && otherMinZ < testBox.globalCenter.z + testBox.depthRadius) { - return true; - } + if (testBox.intersects(otherMinX, otherMinY, otherMinZ, otherMaxX, otherMaxY, otherMaxZ)) { + return true; + } } } return false; diff --git a/mcinterfaceneoforge1211/src/main/java/mcinterface1211/WrapperWorld.java b/mcinterfaceneoforge1211/src/main/java/mcinterface1211/WrapperWorld.java index cfae211d2..886ef4e5c 100644 --- a/mcinterfaceneoforge1211/src/main/java/mcinterface1211/WrapperWorld.java +++ b/mcinterfaceneoforge1211/src/main/java/mcinterface1211/WrapperWorld.java @@ -1017,7 +1017,7 @@ public void spawnExplosion(Point3D location, double strength, boolean flames, bo * Helper method to convert a BoundingBox to an AABB. */ public static AABB convert(BoundingBox box) { - return new AABB(box.globalCenter.x - box.widthRadius, box.globalCenter.y - box.heightRadius, box.globalCenter.z - box.depthRadius, box.globalCenter.x + box.widthRadius, box.globalCenter.y + box.heightRadius, box.globalCenter.z + box.depthRadius); + return new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()); } /** @@ -1026,7 +1026,7 @@ public static AABB convert(BoundingBox box) { * creating two AABBs (the conversion and the offset box). */ public static AABB convertWithOffset(BoundingBox box, double x, double y, double z) { - return new AABB(x + box.globalCenter.x - box.widthRadius, y + box.globalCenter.y - box.heightRadius, z + box.globalCenter.z - box.depthRadius, x + box.globalCenter.x + box.widthRadius, y + box.globalCenter.y + box.heightRadius, z + box.globalCenter.z + box.depthRadius); + return new AABB(x + box.getMinX(), y + box.getMinY(), z + box.getMinZ(), x + box.getMaxX(), y + box.getMaxY(), z + box.getMaxZ()); } /** From f99e009de06c1f48d685e3fbc2b9b5893d2f60c4 Mon Sep 17 00:00:00 2001 From: dldev32 Date: Wed, 13 May 2026 23:02:42 +0300 Subject: [PATCH 2/5] Fixed effective armor penetration --- .../entities/components/AEntityF_Multipart.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java index 6305eb98a..1192d6db5 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java @@ -360,9 +360,12 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, if (bullet != null) { double armorThickness = hitEntry.box.definition != null ? (bullet.definition.bullet.isHeat && hitEntry.box.groupDef.heatArmorThickness != 0 ? hitEntry.box.groupDef.heatArmorThickness : hitEntry.box.groupDef.armorThickness) : 0; double effectiveArmorThickness = getEffectiveArmorThickness(hitEntry, bullet, armorThickness); - double penetrationPotential = bullet.definition.bullet.isHeat ? bullet.definition.bullet.armorPenetration : (bullet.definition.bullet.armorPenetration * bullet.velocity / bullet.initialVelocity); + double penetrationPotential = bullet.definition.bullet.armorPenetration; + if (!bullet.definition.bullet.isHeat && bullet.initialVelocity > 0) { + penetrationPotential *= Math.min(bullet.velocity / bullet.initialVelocity, 1D); + } bullet.armorPenetrated += effectiveArmorThickness; - bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness + " EFFECTIVE: " + (int) effectiveArmorThickness); + bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness + " EFFECTIVE: " + (int) effectiveArmorThickness + " PEN: " + (int) penetrationPotential); if (bullet.armorPenetrated > penetrationPotential) { //Bullet hit too much armor. From b45537c18df66a6d5052071ecc9de0ade9aff109 Mon Sep 17 00:00:00 2001 From: dldev32 Date: Thu, 14 May 2026 18:20:42 +0300 Subject: [PATCH 3/5] Added explosionResistance for collision groups. --- .../baseclasses/Explosion.java | 51 +++++++++++++++++-- .../jsondefs/JSONCollisionBox.java | 32 ++++++------ .../jsondefs/JSONCollisionGroup.java | 31 ++++++----- 3 files changed, 79 insertions(+), 35 deletions(-) diff --git a/mccore/src/main/java/minecrafttransportsimulator/baseclasses/Explosion.java b/mccore/src/main/java/minecrafttransportsimulator/baseclasses/Explosion.java index 80cac25a2..4e4551d9b 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/baseclasses/Explosion.java +++ b/mccore/src/main/java/minecrafttransportsimulator/baseclasses/Explosion.java @@ -12,6 +12,7 @@ import minecrafttransportsimulator.entities.instances.PartGun; import minecrafttransportsimulator.jsondefs.JSONBullet; import minecrafttransportsimulator.jsondefs.JSONBullet.BulletType; +import minecrafttransportsimulator.jsondefs.JSONCollisionGroup; import minecrafttransportsimulator.jsondefs.JSONCollisionGroup.CollisionType; import minecrafttransportsimulator.jsondefs.JSONPotionEffect; import minecrafttransportsimulator.mcinterface.AWrapperWorld; @@ -162,7 +163,8 @@ private void doEntityDamage() { baseDamage = blastDamageVsGround != 0 ? blastDamageVsGround : (blastDamageVsVehicles != 0 ? blastDamageVsVehicles : blastDamage); } - double damageAmount = baseDamage * blastHit.falloff; + double explosionResistance = getExplosionResistance(blastHit.hitPosition, multiparts); + double damageAmount = getBlastDamageAfterResistance(baseDamage, blastHit.falloff, explosionResistance); Damage damage = new Damage(damageAmount, blastHit.box, gun, entityResponsible, deathLanguage).setExplosive().ignoreCooldown().bypassIgnoredExplosiveDamage(); //Apply knockback away from explosion center. @@ -213,7 +215,8 @@ private void doEntityDamage() { } float baseDamage = blastDamageVsLiving != 0 ? blastDamageVsLiving : blastDamage; - double damageAmount = baseDamage * damageFalloff; + double explosionResistance = getExplosionResistance(entityBlastPoint, multiparts); + double damageAmount = getBlastDamageAfterResistance(baseDamage, damageFalloff, explosionResistance); Damage damage = new Damage(damageAmount, blastBounds, gun, entityResponsible, deathLanguage).setExplosive().ignoreCooldown(); //Apply MC-style knockback: use entity eye height for the Y delta, creating natural upward arcs. @@ -468,6 +471,36 @@ private static double getEllipticalFalloff(double dx, double dy, double dz, doub return 1.0 - normDist; } + private static double getBlastDamageAfterResistance(double baseDamage, double falloff, double explosionResistance) { + return Math.max(baseDamage - explosionResistance, 0) * falloff; + } + + /** + * Gets the combined explosion resistance from collision groups between the blast source and target. + * Each group is counted once even if the ray intersects multiple boxes in that group. + */ + private double getExplosionResistance(Point3D targetPosition, List> multiparts) { + Point3D delta = targetPosition.copy().subtract(position); + double pathLength = delta.length(); + if (pathLength < MIN_PATH_LENGTH) { + return 0; + } + + double explosionResistance = 0; + Set resistedGroups = new HashSet<>(); + Point3D rayEnd = targetPosition.copy().addScaled(delta, 0.01D / pathLength); + for (AEntityF_Multipart multipart : multiparts) { + for (BoundingBox box : multipart.allCollisionBoxes) { + JSONCollisionGroup groupDef = box.groupDef; + if (groupDef != null && groupDef.explosionResistance > 0 && !resistedGroups.contains(groupDef) && box.getIntersection(position, rayEnd) != null) { + explosionResistance += groupDef.explosionResistance; + resistedGroups.add(groupDef); + } + } + } + return explosionResistance; + } + /** * Returns the best exposed attack box on a vehicle for this explosion, or null if the vehicle * is outside the damage ellipsoid or fully blocked by terrain. @@ -500,9 +533,17 @@ private BlastHit getBestVehicleBlastHit(EntityVehicleF_Physics vehicle, Bounding * apply to large vehicles by their hull hitboxes rather than only by their entity center. */ private static void getClosestPointOnBox(BoundingBox box, Point3D point, Point3D output) { - output.x = Math.max(box.globalCenter.x - box.widthRadius, Math.min(point.x, box.globalCenter.x + box.widthRadius)); - output.y = Math.max(box.globalCenter.y - box.heightRadius, Math.min(point.y, box.globalCenter.y + box.heightRadius)); - output.z = Math.max(box.globalCenter.z - box.depthRadius, Math.min(point.z, box.globalCenter.z + box.depthRadius)); + if (box.isOBB()) { + output.set(point).subtract(box.globalCenter).reOrigin(box.orientation); + output.x = Math.max(-box.widthRadius, Math.min(output.x, box.widthRadius)); + output.y = Math.max(-box.heightRadius, Math.min(output.y, box.heightRadius)); + output.z = Math.max(-box.depthRadius, Math.min(output.z, box.depthRadius)); + output.rotate(box.orientation).add(box.globalCenter); + } else { + output.x = Math.max(box.globalCenter.x - box.widthRadius, Math.min(point.x, box.globalCenter.x + box.widthRadius)); + output.y = Math.max(box.globalCenter.y - box.heightRadius, Math.min(point.y, box.globalCenter.y + box.heightRadius)); + output.z = Math.max(box.globalCenter.z - box.depthRadius, Math.min(point.z, box.globalCenter.z + box.depthRadius)); + } } /** diff --git a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionBox.java b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionBox.java index 993e74fca..2842063e2 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionBox.java +++ b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionBox.java @@ -1,7 +1,7 @@ package minecrafttransportsimulator.jsondefs; -import minecrafttransportsimulator.baseclasses.Point3D; -import minecrafttransportsimulator.baseclasses.RotationMatrix; +import minecrafttransportsimulator.baseclasses.Point3D; +import minecrafttransportsimulator.baseclasses.RotationMatrix; import minecrafttransportsimulator.jsondefs.JSONAction.ActionType; import minecrafttransportsimulator.packloading.JSONParser.JSONDescription; import minecrafttransportsimulator.packloading.JSONParser.JSONRequired; @@ -11,20 +11,20 @@ public class JSONCollisionBox { @JSONDescription("n entry of x, y, and z coordinates that define the center point of where this collision box is relative to the center of the object.") public Point3D pos; - @JSONDescription("The width of this collision box, in meters. Note that since the pos parameter is the center of the box the box will actually extend � the width in the X and Z direction.") - public float width; - - @JSONDescription("The length of this collision box, in meters. If omitted, width will be used for the Z direction.") - public float length; - - @JSONDescription("Same as width, just for the Y direction.") - public float height; - - @JSONDescription("The rotation of this collision box. Only used if the owning collision group has isOBB set to true.") - public RotationMatrix rot; - - @JSONDescription("If true, the collision box will behave like a ground device set to float. Note that if you make a boat that uses only these boxes, you'll need one for every corner like you would wheels on a car. Failing to do so will result in your boat doing a Titanic, just without an iceberg.") - public boolean collidesWithLiquids; + @JSONDescription("The width of this collision box, in meters. Note that since the pos parameter is the center of the box the box will actually extend � the width in the X and Z direction.") + public float width; + + @JSONDescription("The length of this collision box, in meters. If omitted, width will be used for the Z direction.") + public float length; + + @JSONDescription("Same as width, just for the Y direction.") + public float height; + + @JSONDescription("The rotation of this collision box. Only used if the owning collision group has isOBB set to true.") + public RotationMatrix rot; + + @JSONDescription("If true, the collision box will behave like a ground device set to float. Note that if you make a boat that uses only these boxes, you'll need one for every corner like you would wheels on a car. Failing to do so will result in your boat doing a Titanic, just without an iceberg.") + public boolean collidesWithLiquids; @JSONDescription("The action to perform when clicking this hitbox, if any.") public JSONAction action; diff --git a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java index 3539d30dd..a2466e8ce 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java +++ b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java @@ -1,10 +1,10 @@ package minecrafttransportsimulator.jsondefs; -import java.util.List; -import java.util.Set; - -import minecrafttransportsimulator.packloading.JSONParser.JSONDescription; -import minecrafttransportsimulator.packloading.JSONParser.JSONRequired; +import java.util.List; +import java.util.Set; + +import minecrafttransportsimulator.packloading.JSONParser.JSONDescription; +import minecrafttransportsimulator.packloading.JSONParser.JSONRequired; public class JSONCollisionGroup { @@ -14,6 +14,9 @@ public class JSONCollisionGroup { @JSONDescription("Like normal armor thickness, but used only if a bullet is a HEAT bullet.") public float heatArmorThickness; + @JSONDescription("How much blast damage this group absorbs from explosions. This value reduces blastDamage before distance falloff is applied, and multiple groups between the explosion and target will stack.") + public float explosionResistance; + @JSONDescription("How much to multiply the damage for bullets that hit this group by. Is 1.0 if not specified.") public float damageMultiplier; @@ -29,15 +32,15 @@ public class JSONCollisionGroup { @JSONDescription("How much health this collision group has. When the health reaches 0, this collision group will be disabled. Useful for armor that can only take so many hits, or parts of models that you want to break off when hit. If this value is 0, then the collision group will always be present, and damage inflicted will be charged to the entity rather than this collision group.") public int health; - @JSONDescription("If this is set, then this collision group will first use the animations for this object from the rendering section instead of the animations in this one. If the specified object has applyAfter on it itself, then the animations will be gotten recursively until an applyAfter is not found.") - public String applyAfter; - - @JSONDescription("If true, this collision group will use oriented bounding box logic. This only applies to BULLET, ATTACK, VEHICLE, and CLICK collision types.") - public boolean isOBB; - - @JSONRequired - @JSONDescription("The types of collision for this group.") - public Set collisionTypes; + @JSONDescription("If this is set, then this collision group will first use the animations for this object from the rendering section instead of the animations in this one. If the specified object has applyAfter on it itself, then the animations will be gotten recursively until an applyAfter is not found.") + public String applyAfter; + + @JSONDescription("If true, this collision group will use oriented bounding box logic. This allows to specify rotations and dimensions along all three axes.") + public boolean isOBB; + + @JSONRequired + @JSONDescription("The types of collision for this group.") + public Set collisionTypes; @JSONRequired @JSONDescription("A listing of collisions for this group.") From 81ee727b5084148c162b97407f949df3ace4abb4 Mon Sep 17 00:00:00 2001 From: dldev32 Date: Thu, 21 May 2026 22:33:22 +0300 Subject: [PATCH 4/5] Fixed calculation of the effective armor and added volumetricArmor parameter to calculate the armor thickness based on the dimensions of the collision box --- .../components/AEntityF_Multipart.java | 52 ++++++++++++++----- .../jsondefs/JSONCollisionGroup.java | 3 ++ 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java index 1192d6db5..e674aef70 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java @@ -292,8 +292,9 @@ public Collection getHitBoxes(Point3D pathStart, Point3D p if (hitResult != null) { double boxDistance = hitResult.position.distanceTo(pathStart); boolean addBox = true; - if (box.groupDef != null) { - //Don't add boxes within the same group. + boolean groupHasArmor = box.groupDef != null && (box.groupDef.volumetricArmor || box.groupDef.armorThickness != 0 || box.groupDef.heatArmorThickness != 0); + if (box.groupDef != null && !groupHasArmor) { + //Don't add boxes within the same non-armor group. Iterator> iterator = hitBoxes.entrySet().iterator(); while (iterator.hasNext()) { Entry entry = iterator.next(); @@ -355,17 +356,18 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, } //Check armor pen and see if we hit too much and need to stop processing. - if (hitEntry.box.groupDef != null && (hitEntry.box.groupDef.armorThickness != 0 || hitEntry.box.groupDef.heatArmorThickness != 0)) { + if (hitEntry.box.groupDef != null && (hitEntry.box.groupDef.volumetricArmor || hitEntry.box.groupDef.armorThickness != 0 || hitEntry.box.groupDef.heatArmorThickness != 0)) { hitOperationalHitbox = true; if (bullet != null) { - double armorThickness = hitEntry.box.definition != null ? (bullet.definition.bullet.isHeat && hitEntry.box.groupDef.heatArmorThickness != 0 ? hitEntry.box.groupDef.heatArmorThickness : hitEntry.box.groupDef.armorThickness) : 0; - double effectiveArmorThickness = getEffectiveArmorThickness(hitEntry, bullet, armorThickness); + Point3D bulletDirection = bullet.motion.copy().normalize(); + double armorThickness = hitEntry.box.groupDef.volumetricArmor ? getPhysicalArmorThickness(hitEntry, bulletDirection, true) : (hitEntry.box.definition != null ? (bullet.definition.bullet.isHeat && hitEntry.box.groupDef.heatArmorThickness != 0 ? hitEntry.box.groupDef.heatArmorThickness : hitEntry.box.groupDef.armorThickness) : 0); + double effectiveArmorThickness = hitEntry.box.groupDef.volumetricArmor ? armorThickness : getEffectiveArmorThickness(hitEntry, bulletDirection, armorThickness); double penetrationPotential = bullet.definition.bullet.armorPenetration; if (!bullet.definition.bullet.isHeat && bullet.initialVelocity > 0) { penetrationPotential *= Math.min(bullet.velocity / bullet.initialVelocity, 1D); } bullet.armorPenetrated += effectiveArmorThickness; - bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness + " EFFECTIVE: " + (int) effectiveArmorThickness + " PEN: " + (int) penetrationPotential); + bullet.displayDebugMessage("HIT ARMOR OF: " + (int) armorThickness + " EFFECTIVE: " + (int) effectiveArmorThickness + " TOTAL: " + (int) bullet.armorPenetrated + " PEN: " + (int) penetrationPotential); if (bullet.armorPenetrated > penetrationPotential) { //Bullet hit too much armor. @@ -430,17 +432,43 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, return null; } - private double getEffectiveArmorThickness(BoundingBoxHitResult hitEntry, EntityBullet bullet, double armorThickness) { + private double getEffectiveArmorThickness(BoundingBoxHitResult hitEntry, Point3D bulletDirection, double armorThickness) { if (armorThickness == 0) { return 0; } - Point3D bulletDirection = bullet.motion.copy().normalize(); - Point3D hitNormal = hitEntry.normal.copy().normalize(); - if (bulletDirection.isZero() || hitNormal.isZero()) { + Point3D armorNormal = getArmorNormal(hitEntry); + if (bulletDirection.isZero() || armorNormal.isZero()) { return armorThickness; } - double armorImpactFactor = Math.max(Math.abs(hitNormal.dotProduct(bulletDirection, true)), 0.001D); - return armorThickness / armorImpactFactor; + double armorImpactFactor = Math.max(Math.abs(armorNormal.dotProduct(bulletDirection, true)), 0.001D); + double effectiveThickness = armorThickness / armorImpactFactor; + double physicalThickness = getPhysicalArmorThickness(hitEntry, bulletDirection, false); + return Math.min(effectiveThickness, physicalThickness); + } + + private Point3D getArmorNormal(BoundingBoxHitResult hitEntry) { + BoundingBox box = hitEntry.box; + Point3D armorNormal = new Point3D(0, 0, 1); + if (box.isOBB()) { + armorNormal.rotate(box.orientation); + } + return armorNormal.normalize(); + } + + private double getPhysicalArmorThickness(BoundingBoxHitResult hitEntry, Point3D bulletDirection, boolean includeLength) { + BoundingBox box = hitEntry.box; + Point3D localDirection = box.isOBB() ? bulletDirection.copy().reOrigin(box.orientation) : bulletDirection.copy(); + double physicalThickness = Double.MAX_VALUE; + if (Math.abs(localDirection.x) > 1.0E-7D) { + physicalThickness = Math.min(physicalThickness, 2D * box.widthRadius / Math.abs(localDirection.x)); + } + if (Math.abs(localDirection.y) > 1.0E-7D) { + physicalThickness = Math.min(physicalThickness, 2D * box.heightRadius / Math.abs(localDirection.y)); + } + if (includeLength && Math.abs(localDirection.z) > 1.0E-7D) { + physicalThickness = Math.min(physicalThickness, 2D * box.depthRadius / Math.abs(localDirection.z)); + } + return physicalThickness * 1000D; } @Override diff --git a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java index a2466e8ce..305f14ffa 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java +++ b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java @@ -14,6 +14,9 @@ public class JSONCollisionGroup { @JSONDescription("Like normal armor thickness, but used only if a bullet is a HEAT bullet.") public float heatArmorThickness; + @JSONDescription("If true, armor thickness is calculated from the actual path length through the collision box volume instead of armorThickness. If false, armorThickness is used and the box length is ignored for armor calculations.") + public boolean volumetricArmor; + @JSONDescription("How much blast damage this group absorbs from explosions. This value reduces blastDamage before distance falloff is applied, and multiple groups between the explosion and target will stack.") public float explosionResistance; From ef228bb882748107b5b5093da543ad8008d79756 Mon Sep 17 00:00:00 2001 From: dldev32 Date: Thu, 6 Aug 2026 20:28:21 +0300 Subject: [PATCH 5/5] Fixed armor calculation on non-volemetric armor --- .../components/AEntityF_Multipart.java | 19 +++++-------------- .../jsondefs/JSONCollisionGroup.java | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java index e674aef70..9d36ef396 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java +++ b/mccore/src/main/java/minecrafttransportsimulator/entities/components/AEntityF_Multipart.java @@ -360,7 +360,7 @@ public EntityBullet.HitType attackProjectile(Damage damage, EntityBullet bullet, hitOperationalHitbox = true; if (bullet != null) { Point3D bulletDirection = bullet.motion.copy().normalize(); - double armorThickness = hitEntry.box.groupDef.volumetricArmor ? getPhysicalArmorThickness(hitEntry, bulletDirection, true) : (hitEntry.box.definition != null ? (bullet.definition.bullet.isHeat && hitEntry.box.groupDef.heatArmorThickness != 0 ? hitEntry.box.groupDef.heatArmorThickness : hitEntry.box.groupDef.armorThickness) : 0); + double armorThickness = hitEntry.box.groupDef.volumetricArmor ? getPhysicalArmorThickness(hitEntry, bulletDirection) : (hitEntry.box.definition != null ? (bullet.definition.bullet.isHeat && hitEntry.box.groupDef.heatArmorThickness != 0 ? hitEntry.box.groupDef.heatArmorThickness : hitEntry.box.groupDef.armorThickness) : 0); double effectiveArmorThickness = hitEntry.box.groupDef.volumetricArmor ? armorThickness : getEffectiveArmorThickness(hitEntry, bulletDirection, armorThickness); double penetrationPotential = bullet.definition.bullet.armorPenetration; if (!bullet.definition.bullet.isHeat && bullet.initialVelocity > 0) { @@ -436,26 +436,17 @@ private double getEffectiveArmorThickness(BoundingBoxHitResult hitEntry, Point3D if (armorThickness == 0) { return 0; } - Point3D armorNormal = getArmorNormal(hitEntry); + Point3D armorNormal = hitEntry.normal.copy().normalize(); if (bulletDirection.isZero() || armorNormal.isZero()) { return armorThickness; } double armorImpactFactor = Math.max(Math.abs(armorNormal.dotProduct(bulletDirection, true)), 0.001D); double effectiveThickness = armorThickness / armorImpactFactor; - double physicalThickness = getPhysicalArmorThickness(hitEntry, bulletDirection, false); + double physicalThickness = getPhysicalArmorThickness(hitEntry, bulletDirection); return Math.min(effectiveThickness, physicalThickness); } - private Point3D getArmorNormal(BoundingBoxHitResult hitEntry) { - BoundingBox box = hitEntry.box; - Point3D armorNormal = new Point3D(0, 0, 1); - if (box.isOBB()) { - armorNormal.rotate(box.orientation); - } - return armorNormal.normalize(); - } - - private double getPhysicalArmorThickness(BoundingBoxHitResult hitEntry, Point3D bulletDirection, boolean includeLength) { + private double getPhysicalArmorThickness(BoundingBoxHitResult hitEntry, Point3D bulletDirection) { BoundingBox box = hitEntry.box; Point3D localDirection = box.isOBB() ? bulletDirection.copy().reOrigin(box.orientation) : bulletDirection.copy(); double physicalThickness = Double.MAX_VALUE; @@ -465,7 +456,7 @@ private double getPhysicalArmorThickness(BoundingBoxHitResult hitEntry, Point3D if (Math.abs(localDirection.y) > 1.0E-7D) { physicalThickness = Math.min(physicalThickness, 2D * box.heightRadius / Math.abs(localDirection.y)); } - if (includeLength && Math.abs(localDirection.z) > 1.0E-7D) { + if (Math.abs(localDirection.z) > 1.0E-7D) { physicalThickness = Math.min(physicalThickness, 2D * box.depthRadius / Math.abs(localDirection.z)); } return physicalThickness * 1000D; diff --git a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java index 305f14ffa..3ba11f7b5 100644 --- a/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java +++ b/mccore/src/main/java/minecrafttransportsimulator/jsondefs/JSONCollisionGroup.java @@ -14,7 +14,7 @@ public class JSONCollisionGroup { @JSONDescription("Like normal armor thickness, but used only if a bullet is a HEAT bullet.") public float heatArmorThickness; - @JSONDescription("If true, armor thickness is calculated from the actual path length through the collision box volume instead of armorThickness. If false, armorThickness is used and the box length is ignored for armor calculations.") + @JSONDescription("If true, armor thickness is calculated from the actual path length through the collision box volume instead of armorThickness. If false, armorThickness is applied to the face that was hit and adjusted for the impact angle, with the box dimensions only limiting grazing impacts.") public boolean volumetricArmor; @JSONDescription("How much blast damage this group absorbs from explosions. This value reduces blastDamage before distance falloff is applied, and multiple groups between the explosion and target will stack.")