总之做了初步的OBB碰撞检测

This commit is contained in:
17146 2025-06-21 00:19:00 +08:00 committed by Light_Quanta
parent 1f5841da89
commit e42abf1300
No known key found for this signature in database
GPG key ID: 11A39A1B8C890959
3 changed files with 224 additions and 1 deletions

View file

@ -1,9 +1,13 @@
package com.atsuishio.superbwarfare.mixins;
import com.atsuishio.superbwarfare.entity.OBBEntity;
import com.atsuishio.superbwarfare.entity.mixin.OBBHitter;
import com.atsuishio.superbwarfare.entity.vehicle.base.MobileVehicleEntity;
import com.atsuishio.superbwarfare.tools.OBB;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@ -13,7 +17,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Entity.class)
public class EntityMixin implements OBBHitter {
public abstract class EntityMixin implements OBBHitter {
/**
* From Automobility
@ -24,6 +28,18 @@ public class EntityMixin implements OBBHitter {
@Shadow
private boolean onGround;
@Shadow
public abstract Level level();
@Shadow
public abstract AABB getBoundingBox();
@Shadow
public abstract Vec3 position();
@Shadow
public abstract Vec3 getEyePosition();
@Inject(method = "collide", at = @At("HEAD"))
private void sbw$spoofGroundStart(Vec3 movement, CallbackInfoReturnable<Vec3> cir) {
if (MobileVehicleEntity.IGNORE_ENTITY_GROUND_CHECK_STEPPING) {
@ -53,6 +69,27 @@ public class EntityMixin implements OBBHitter {
this.sbw$currentHitPart = part;
}
// TODO 优化OBB面算法并排除AABB影响现在下车就动不了了
@Inject(method = "collide", at = @At("HEAD"), cancellable = true)
private void onHitOBB(Vec3 movement, CallbackInfoReturnable<Vec3> cir) {
AABB boundingBox = this.getBoundingBox();
Entity self = (Entity) (Object) this;
if (self instanceof Player player) {
boundingBox = player.getLocalBoundsForPose(player.getPose());
}
var list = this.level().getEntities(self, boundingBox.expandTowards(movement));
var entity = list.stream().filter(e -> e instanceof OBBEntity).findFirst().orElse(null);
if (entity == null || entity == self) return;
OBBEntity obbEntity = (OBBEntity) entity;
Vec3 feetPos = this.position().subtract(this.getEyePosition());
// 第一版实现
var faceInfo = OBB.findClosestFace(obbEntity.getOBBs(), feetPos);
if (faceInfo == null) return;
double dot = movement.dot(new Vec3(faceInfo.faceNormal()));
var vec = new Vec3(faceInfo.faceNormal()).multiply(dot, dot, dot);
cir.setReturnValue(movement.subtract(vec));
}
// TODO 优化后续逻辑
// @Redirect(method = "turn(DD)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;setXRot(F)V", ordinal = 1))
// public void turn(Entity instance, float pXRot) {

View file

@ -26,6 +26,7 @@ public abstract class LevelMixin {
@Inject(method = "getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;)Ljava/util/List;",
at = @At("RETURN"))
public void getEntities(Entity pEntity, AABB pBoundingBox, Predicate<? super Entity> pPredicate, CallbackInfoReturnable<List<Entity>> cir) {
// TODO 研究OBB碰撞的时候把这个删了
if (!(pEntity instanceof Projectile)) return;
StreamSupport.stream(this.getEntities().getAll().spliterator(), false).filter(e -> e instanceof OBBEntity && pPredicate.test(e))

View file

@ -2,11 +2,13 @@ package com.atsuishio.superbwarfare.tools;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Intersectionf;
import org.joml.Math;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.List;
import java.util.Optional;
/**
@ -257,6 +259,189 @@ public record OBB(Vector3f center, Vector3f extents, Quaternionf rotation, Part
projZ <= extents.z;
}
// 获取最近面的全局法向量
public Vector3f getClosestFaceNormal(Vec3 vec3) {
// 转换玩家位置到Vector3f
Vector3f pos = new Vector3f((float) vec3.x, (float) vec3.y, (float) vec3.z);
// 1. 转换到局部坐标系
Vector3f localPos = new Vector3f(pos).sub(center); // 减去中心
Quaternionf invRotation = new Quaternionf(rotation).invert(); // 旋转的逆
invRotation.transform(localPos); // 应用逆旋转
// 2. 计算到六个面的距离
float[] distances = new float[6];
distances[0] = Math.abs(localPos.x - extents.x); // +X
distances[1] = Math.abs(localPos.x + extents.x); // -X
distances[2] = Math.abs(localPos.y - extents.y); // +Y
distances[3] = Math.abs(localPos.y + extents.y); // -Y
distances[4] = Math.abs(localPos.z - extents.z); // +Z
distances[5] = Math.abs(localPos.z + extents.z); // -Z
// 3. 找到最近面的索引
int minIndex = 0;
for (int i = 1; i < distances.length; i++) {
if (distances[i] < distances[minIndex]) {
minIndex = i;
}
}
// 4. 获取局部法向量并转换到全局坐标系
Vector3f localNormal = getLocalNormalByIndex(minIndex);
Vector3f globalNormal = new Vector3f(localNormal);
rotation.transform(globalNormal);
globalNormal.normalize(); // 确保单位长度
return globalNormal;
}
// 根据索引返回局部法向量
private Vector3f getLocalNormalByIndex(int index) {
return switch (index) {
case 0 -> new Vector3f(1, 0, 0); // +X
case 1 -> new Vector3f(-1, 0, 0); // -X
case 2 -> new Vector3f(0, 1, 0); // +Y
case 3 -> new Vector3f(0, -1, 0); // -Y
case 4 -> new Vector3f(0, 0, 1); // +Z
case 5 -> new Vector3f(0, 0, -1); // -Z
default -> throw new IllegalArgumentException("Invalid face index");
};
}
// 计算OBB的包围球中心点相同半径为对角线长度
public float getBoundingSphereRadius() {
return extents.length();
}
// 获取面的信息全局中心点和法向量
public FaceInfo getFaceInfo(int faceIndex) {
// 局部坐标系的面法向量
Vector3f localNormal = getLocalNormalByIndex(faceIndex);
// 局部中心点从OBB中心指向该面中心
Vector3f localCenter = new Vector3f(localNormal).mul(extents);
// 转换到全局坐标系
Vector3f globalCenter = new Vector3f(localCenter);
rotation.transform(globalCenter);
globalCenter.add(center);
Vector3f globalNormal = new Vector3f(localNormal);
rotation.transform(globalNormal);
globalNormal.normalize(); // 确保单位向量
return new FaceInfo(globalCenter, globalNormal);
}
// 计算玩家位置到指定面的距离
public float distanceToFace(int faceIndex, Vector3f playerPos) {
FaceInfo faceInfo = getFaceInfo(faceIndex);
Vector3f diff = new Vector3f(playerPos).sub(faceInfo.center());
return Math.abs(diff.dot(faceInfo.normal()));
}
// 存储面信息
public record FaceInfo(Vector3f center, Vector3f normal) {
}
// 计算点到OBB的距离平方
public float distanceSquaredToPoint(Vector3f point) {
Vector3f localPoint = new Vector3f(point).sub(center);
Quaternionf invRotation = new Quaternionf(rotation).invert();
invRotation.transform(localPoint);
Vector3f closestPoint = new Vector3f();
closestPoint.x = Math.max(-extents.x, Math.min(localPoint.x, extents.x));
closestPoint.y = Math.max(-extents.y, Math.min(localPoint.y, extents.y));
closestPoint.z = Math.max(-extents.z, Math.min(localPoint.z, extents.z));
return localPoint.distanceSquared(closestPoint);
}
/**
* 寻找距离某一个位置最近的OBB
*/
@Nullable
public static OBB findClosestOBB(List<OBB> obbList, Vec3 vec3) {
if (obbList == null || obbList.isEmpty()) {
return null;
}
Vector3f pos = vec3.toVector3f();
OBB closestOBB = null;
float minDistanceSq = Float.MAX_VALUE;
for (OBB obb : obbList) {
float distToCenterSq = pos.distanceSquared(obb.center());
float boundingRadiusSq = obb.getBoundingSphereRadius() * obb.getBoundingSphereRadius();
if (distToCenterSq - boundingRadiusSq > minDistanceSq) {
continue;
}
float distSq = obb.distanceSquaredToPoint(pos);
if (distSq < minDistanceSq) {
minDistanceSq = distSq;
closestOBB = obb;
}
}
return closestOBB;
}
// 查找最近的面
@Nullable
public static ClosestFaceResult findClosestFace(List<OBB> obbList, Vec3 playerPos) {
if (obbList == null || obbList.isEmpty()) {
return null;
}
Vector3f pos = new Vector3f((float) playerPos.x, (float) playerPos.y, (float) playerPos.z);
OBB closestOBB = null;
int closestFaceIndex = -1;
float minDistance = Float.MAX_VALUE;
Vector3f closestFaceNormal = null;
Vector3f closestFaceCenter = null;
// 第一阶段使用包围球快速筛选候选OBB
for (OBB obb : obbList) {
// 计算玩家到OBB中心的距离
float distToCenter = pos.distance(obb.center());
// 如果距离大于包围球半径不可能比当前最小值更近
if (distToCenter - obb.getBoundingSphereRadius() > minDistance) {
continue;
}
// 第二阶段检查该OBB的所有面
for (int faceIndex = 0; faceIndex < 6; faceIndex++) {
float dist = obb.distanceToFace(faceIndex, pos);
// 更新最小距离
if (dist < minDistance) {
minDistance = dist;
closestOBB = obb;
closestFaceIndex = faceIndex;
OBB.FaceInfo faceInfo = obb.getFaceInfo(faceIndex);
closestFaceNormal = faceInfo.normal();
closestFaceCenter = faceInfo.center();
}
}
}
if (closestOBB == null) {
return null;
}
return new ClosestFaceResult(closestOBB, closestFaceIndex, minDistance,
closestFaceCenter, closestFaceNormal);
}
// 存储最近面结果
public record ClosestFaceResult(OBB obb, int faceIndex, float distance, Vector3f faceCenter, Vector3f faceNormal) {
}
public enum Part {
EMPTY,
WHEEL_LEFT,