diff --git a/src/main/java/cam72cam/immersiverailroading/entity/EntityRidableRollingStock.java b/src/main/java/cam72cam/immersiverailroading/entity/EntityRidableRollingStock.java index d10f567a7..baa5674d1 100644 --- a/src/main/java/cam72cam/immersiverailroading/entity/EntityRidableRollingStock.java +++ b/src/main/java/cam72cam/immersiverailroading/entity/EntityRidableRollingStock.java @@ -3,22 +3,17 @@ import cam72cam.immersiverailroading.Config; import cam72cam.immersiverailroading.ImmersiveRailroading; import cam72cam.immersiverailroading.entity.EntityCoupleableRollingStock.CouplerType; -import cam72cam.immersiverailroading.floor.NavMesh; import cam72cam.immersiverailroading.library.Permissions; import cam72cam.immersiverailroading.model.part.Door; import cam72cam.immersiverailroading.model.part.Seat; import cam72cam.immersiverailroading.render.ExpireableMap; -import cam72cam.immersiverailroading.util.MathUtil; -import cam72cam.immersiverailroading.util.VecUtil; import cam72cam.mod.entity.Entity; import cam72cam.mod.entity.Player; -import cam72cam.mod.entity.boundingbox.IBoundingBox; import cam72cam.mod.entity.custom.IRidable; import cam72cam.mod.entity.sync.TagSync; import cam72cam.mod.item.ClickResult; import cam72cam.mod.item.ItemStack; import cam72cam.mod.math.Vec3d; -import cam72cam.mod.model.obj.OBJFace; import cam72cam.mod.serialization.SerializationException; import cam72cam.mod.serialization.TagCompound; import cam72cam.mod.serialization.TagField; @@ -63,7 +58,7 @@ public ClickResult onClick(Player player, Player.Hand hand) { } } - protected Vec3d getSeatPosition(UUID passenger) { + private Vec3d getSeatPosition(UUID passenger) { String seat = seatedPassengers.entrySet().stream() .filter(x -> x.getValue().equals(passenger)) .map(Map.Entry::getKey).findFirst().orElse(null); @@ -75,45 +70,31 @@ protected Vec3d getSeatPosition(UUID passenger) { @Override public Vec3d getMountOffset(Entity passenger, Vec3d off) { - NavMesh navMesh = getDefinition().navMesh; + if (passenger.isVillager() && !payingPassengerPositions.containsKey(passenger.getUUID())) { + payingPassengerPositions.put(passenger.getUUID(), passenger.getPosition()); + } + + if (passenger.isVillager() && !seatedPassengers.containsValue(passenger.getUUID())) { + for (Seat seat : getDefinition().getModel().getSeats()) { + if (!seatedPassengers.containsKey(seat.part.key)) { + seatedPassengers.put(seat.part.key, passenger.getUUID()); + break; + } + } + } - off = off.scale(gauge.scale()); Vec3d seat = getSeatPosition(passenger.getUUID()); if (seat != null) { return seat; } - Vec3d realOffset = off.rotateYaw(-90); - IBoundingBox queryBox = IBoundingBox.from( - realOffset.subtract(4f, 4f, 4f), - realOffset.add(4f, 4f, 4f) - ); - - List nearby = new ArrayList<>(); - navMesh.queryBVH(navMesh.root, queryBox, nearby, this.gauge.scale()); - - Vec3d closestPoint = null; - double closestDistanceSq = Double.MAX_VALUE; - - for (OBJFace tri : nearby) { - Vec3d p0 = tri.vertex0.pos; - Vec3d p1 = tri.vertex1.pos; - Vec3d p2 = tri.vertex2.pos; - - Vec3d pointOnTri = MathUtil.closestPointOnTriangle(realOffset, p0, p1, p2); - double distSq = realOffset.subtract(pointOnTri).lengthSquared(); - - if (distSq < closestDistanceSq) { - closestDistanceSq = distSq; - closestPoint = pointOnTri; - } + if (passenger.isVillager()) { + int wiggle = 10; + off = off.add((Math.random()-0.5) * wiggle, 0, (Math.random()-0.5) * wiggle); } + off = this.getDefinition().correctPassengerBounds(gauge, off, shouldRiderSit(passenger)); - if (closestPoint != null) { - return closestPoint.rotateYaw(90); - } else { - return new Vec3d(0, 0, 0); - } + return off; } @Override @@ -132,53 +113,22 @@ public boolean shouldRiderSit(Entity passenger) { @Override public Vec3d onPassengerUpdate(Entity passenger, Vec3d offset) { - Vec3d movement = new Vec3d(0, 0, 0); if (passenger.isPlayer()) { - movement = playerMovement(passenger.asPlayer(), offset); - } - Vec3d targetXZ = VecUtil.rotatePitch(movement, -this.getRotationPitch()); - - Vec3d rayStart = targetXZ.rotateYaw(-90).add(0, 1, 0); - Vec3d rayDir = new Vec3d(0, -1, 0); - - Vec3d localTarget = targetXZ.rotateYaw(-90); - - IBoundingBox rayBox = IBoundingBox.from( - localTarget.subtract(0.5f, 0.5f, 0.5f), - localTarget.add(0.5f, 0.5f, 0.5f) - ); - List nearby = new ArrayList<>(); - NavMesh navMesh = getDefinition().navMesh; - navMesh.queryBVH(navMesh.root, rayBox, nearby, this.gauge.scale()); - - double closestY = Float.NEGATIVE_INFINITY; - boolean hit = false; - - for(OBJFace tri : nearby) { - Double t = MathUtil.intersectRayTriangle(rayStart, rayDir, tri); - if (t != null && t >= 0) { - Vec3d hitPoint = rayStart.add(rayDir.scale(t)); - if (!hit || hitPoint.y > closestY) { - closestY = hitPoint.y; - hit = true; - } - } - - } - - if (hit) { - offset = VecUtil.rotatePitch(new Vec3d(targetXZ.x, closestY, targetXZ.z), this.getRotationPitch()); + offset = playerMovement(passenger.asPlayer(), offset); } Vec3d seat = getSeatPosition(passenger.getUUID()); if (seat != null) { offset = seat; + } else { + offset = this.getDefinition().correctPassengerBounds(gauge, offset, shouldRiderSit(passenger)); } + offset = offset.add(0, Math.sin(Math.toRadians(this.getRotationPitch())) * offset.z, 0); return offset; } - protected boolean isNearestDoorOpen(Player source) { + private boolean isNearestDoorOpen(Player source) { // Find any doors that are close enough that are closed (and then negate) return !this.getDefinition().getModel().getDoors().stream() .filter(d -> d.type == Door.Types.CONNECTING) @@ -188,138 +138,63 @@ protected boolean isNearestDoorOpen(Player source) { .isPresent(); } - protected Vec3d playerMovement(Player source, Vec3d offset) { + private Vec3d playerMovement(Player source, Vec3d offset) { Vec3d movement = source.getMovementInput(); - if (movement.length() <= 0.1) { - return offset; + /* + if (sprinting) { + movement = movement.scale(3); + } + */ + if (movement.length() < 0.1) { + return offset; + } + + movement = new Vec3d(movement.x, 0, movement.z).rotateYaw(this.getRotationYaw() - source.getRotationYawHead()); + + if (!getDefinition().hitsNavCollisionMesh(this.gauge, offset, movement)) { + offset = offset.add(movement); } - movement = new Vec3d(movement.x, 0, movement.z).rotateYaw(this.getRotationYaw() - source.getRotationYawHead()); - Vec3d localOffset = offset.rotateYaw(-90).add(movement.rotateYaw(-90)); - - IBoundingBox rayBox = IBoundingBox.from( - localOffset.subtract(0.2f, 0.2f, 0.2f), - localOffset.add(0.2f, 0.2f, 0.2f) - ); - List nearby = new ArrayList<>(); - NavMesh navMesh = getDefinition().navMesh; - navMesh.queryBVH(navMesh.collisionRoot, rayBox, nearby, this.gauge.scale()); + if (this instanceof EntityCoupleableRollingStock) { + EntityCoupleableRollingStock couplable = (EntityCoupleableRollingStock) this; - Vec3d rayStart = localOffset.add(0, 1, 0); - Vec3d rayDir = movement.rotateYaw(-90).normalize(); - - for (OBJFace tri : nearby) { - Double t = MathUtil.intersectRayTriangle(rayStart, rayDir, tri); - if (t != null && t >= 0) { - return offset; - } - } - - if (isDoorOpen(offset, movement)) { - return offset; - } - - offset = offset.add(movement); - - if (getWorld().isServer) { - for (Door door : getDefinition().getModel().getDoors()) { - if (door.isAtOpenDoor(source, this, Door.Types.EXTERNAL)) { - Vec3d doorCenter = door.center(this); - Vec3d toDoor = doorCenter.subtract(offset).normalize(); - double dot = toDoor.dotProduct(movement.normalize()); - if (dot > 0.5) { - this.removePassenger(source); - break; - } - } - } - } - - if (this instanceof EntityCoupleableRollingStock) { - EntityCoupleableRollingStock coupleable = (EntityCoupleableRollingStock) this; - - boolean isAtFront = isAtCoupler(offset, movement, EntityCoupleableRollingStock.CouplerType.FRONT); - boolean isAtBack = isAtCoupler(offset, movement, EntityCoupleableRollingStock.CouplerType.BACK); + boolean atFront = this.getDefinition().isAtFront(gauge, offset); + boolean atBack = this.getDefinition().isAtRear(gauge, offset); + // TODO config for strict doors boolean atDoor = isNearestDoorOpen(source); - isAtFront &= atDoor; - isAtBack &= atDoor; + atFront &= atDoor; + atBack &= atDoor; - for (EntityCoupleableRollingStock.CouplerType coupler : EntityCoupleableRollingStock.CouplerType.values()) { - boolean atCoupler = coupler == EntityCoupleableRollingStock.CouplerType.FRONT ? isAtFront : isAtBack; - if (atCoupler && coupleable.isCoupled(coupler)) { + for (CouplerType coupler : CouplerType.values()) { + boolean atCoupler = coupler == CouplerType.FRONT ? atFront : atBack; + if (atCoupler && couplable.isCoupled(coupler)) { EntityCoupleableRollingStock coupled = ((EntityCoupleableRollingStock) this).getCoupled(coupler); if (coupled != null) { - if (coupled.isNearestDoorOpen(source)) { + if (((EntityRidableRollingStock)coupled).isNearestDoorOpen(source)) { coupled.addPassenger(source); } } else if (this.getTickCount() > 20) { ImmersiveRailroading.info( "Tried to move between cars (%s, %s), but %s was not found", this.getUUID(), - coupleable.getCoupledUUID(coupler), - coupleable.getCoupledUUID(coupler) + couplable.getCoupledUUID(coupler), + couplable.getCoupledUUID(coupler) ); } + return offset; } } - } - - return offset; - } + } - private boolean isDoorOpen(Vec3d start, Vec3d end) { - start = VecUtil.rotatePitch(start, -this.getRotationPitch()); - end = VecUtil.rotatePitch(end, -this.getRotationPitch()); - - start = start.rotateYaw(-90); - end = start.add(end.rotateYaw(-90)); - - List> doors = getDefinition().getModel().getDoors().stream() - .filter(d -> d.type == Door.Types.INTERNAL || d.type == Door.Types.CONNECTING) - .filter(d -> !d.isOpen(this)).collect(Collectors.toList()); - boolean intersects = false; - for (Door door : doors) { - IBoundingBox box = IBoundingBox.from( - door.part.min, - door.part.max - ); - intersects = box.intersectsSegment(start, end); - if (intersects) { - break; - } + if (getDefinition().getModel().getDoors().stream().anyMatch(x -> x.isAtOpenDoor(source, this, Door.Types.EXTERNAL)) && + getWorld().isServer && + !this.getDefinition().correctPassengerBounds(gauge, offset, shouldRiderSit(source)).equals(offset) + ) { + this.removePassenger(source); } - return intersects; - } - private boolean isAtCoupler(Vec3d offset, Vec3d movement, EntityCoupleableRollingStock.CouplerType type) { - offset = offset.rotateYaw(-90); - double coupler = getDefinition().getCouplerPosition(type, this.gauge); - Vec3d couplerPos = new Vec3d(type == EntityCoupleableRollingStock.CouplerType.FRONT ? -coupler : coupler, offset.y, offset.z); - - IBoundingBox queryBox = IBoundingBox.from( - couplerPos.subtract(0.2, 0.2, 0.2), - couplerPos.add(0.2, 0.2, 0.2) - ); - - List nearby = new ArrayList<>(); - NavMesh navMesh = getDefinition().navMesh; - navMesh.queryBVH(navMesh.root, queryBox, nearby, this.gauge.scale()); - - for (OBJFace tri : nearby) { - Vec3d p0 = tri.vertex0.pos; - Vec3d p1 = tri.vertex1.pos; - Vec3d p2 = tri.vertex2.pos; - - Vec3d closestPoint = MathUtil.closestPointOnTriangle(offset, p0, p1, p2); - double distance = offset.subtract(closestPoint).length(); - if (distance < 0.5) { - Vec3d toCoupler = couplerPos.subtract(offset).normalize(); - double dot = toCoupler.dotProduct(movement.rotateYaw(-90).normalize()); - if (dot > 0.5) return true; - } - } - return false; + return offset; } @Override diff --git a/src/main/java/cam72cam/immersiverailroading/floor/NavMesh.java b/src/main/java/cam72cam/immersiverailroading/floor/NavMesh.java index 6c47846af..b182e46db 100644 --- a/src/main/java/cam72cam/immersiverailroading/floor/NavMesh.java +++ b/src/main/java/cam72cam/immersiverailroading/floor/NavMesh.java @@ -15,56 +15,63 @@ import java.util.*; public class NavMesh { - public BVHNode root; - public BVHNode collisionRoot; - private final boolean hasNavMesh; + public final BVHNode root; + public final BVHNode collisionRoot; // Theoretically this could be much lower. IR floor meshes probably won't use the whole depth, but who knows private static final int MAX_DEPTH = 20; private static final int LEAF_SIZE = 8; public NavMesh(EntityRollingStockDefinition definition) { - hasNavMesh = definition.getModel().floor != null; - - if (hasNavMesh) { - initNavMesh(definition.getModel()); + StockModel model = definition.getModel(); + if (model.floor != null) { + root = initFloorMesh(model); + // Correct bounds to match actual bb + Vec3d bounds = model.floor.max.subtract(model.floor.min); + definition.passengerCompartmentLength = bounds.x/2; + definition.passengerCompartmentWidth = bounds.z/2; } else { - try { - initLegacy(definition); - } catch (IllegalArgumentException e) { - ModCore.catching(e); - } + root = initFloorLegacy(definition); } + + collisionRoot = initCollisionMesh(model); } - private void initNavMesh(StockModel model) { + private BVHNode initFloorMesh(StockModel model) { FaceAccessor accessor = model.getFaceAccessor(); List floor = new ArrayList<>(); if (model.floor != null) { - model.floor.groups().forEach(group -> { - FaceAccessor sub = accessor.getSubByGroup(group.name); + model.floor.modelIDs.forEach(group -> { + FaceAccessor sub = accessor.getSubByGroup(group); sub.forEach(a -> floor.add(a.asOBJFace())); }); } - this.root = buildBVH(floor, 0); + return buildBVH(floor, 0); + } + private BVHNode initCollisionMesh(StockModel model) { + FaceAccessor accessor = model.getFaceAccessor(); List collision = new ArrayList<>(); if (model.collision != null) { - model.collision.groups().forEach(group -> { - FaceAccessor sub = accessor.getSubByGroup(group.name); + model.collision.modelIDs.forEach(group -> { + FaceAccessor sub = accessor.getSubByGroup(group); sub.forEach(a -> collision.add(a.asOBJFace())); }); } - this.collisionRoot = buildBVH(collision, 0); + + if (collision.isEmpty()) { + return null; + } + return buildBVH(collision, 0); } - private void initLegacy(EntityRollingStockDefinition def) throws IllegalArgumentException { + private BVHNode initFloorLegacy(EntityRollingStockDefinition def) { Vec3d center = def.passengerCenter; Double length = def.passengerCompartmentLength; Double width = def.passengerCompartmentWidth; if (length == null || width == null) { - throw new IllegalArgumentException(String.format("Rolling stock %s needs to have either a FLOOR object or have \"length\" and \"width\" defined in the \"passenger\" section of the stocks json", def.name())); + throw new RuntimeException(String.format("Rolling stock %s needs to have either a FLOOR object or have \"length\" and \"width\" defined in the \"passenger\" section of the stocks json", def.name())); } if (center == null) { @@ -93,12 +100,7 @@ private void initLegacy(EntityRollingStockDefinition def) throws IllegalArgument face1.normal = normal; face2.normal = normal; - this.root = buildBVH(Arrays.asList(face1, face2), 0); - this.collisionRoot = buildBVH(Collections.emptyList(), 0); - } - - public boolean hasNavMesh() { - return hasNavMesh; + return buildBVH(Arrays.asList(face1, face2), 0); } public static class BVHNode { diff --git a/src/main/java/cam72cam/immersiverailroading/registry/EntityRollingStockDefinition.java b/src/main/java/cam72cam/immersiverailroading/registry/EntityRollingStockDefinition.java index d6e5944bb..829e56a24 100644 --- a/src/main/java/cam72cam/immersiverailroading/registry/EntityRollingStockDefinition.java +++ b/src/main/java/cam72cam/immersiverailroading/registry/EntityRollingStockDefinition.java @@ -13,8 +13,10 @@ import cam72cam.immersiverailroading.model.StockModel; import cam72cam.immersiverailroading.model.components.ModelComponent; import cam72cam.mod.entity.EntityRegistry; +import cam72cam.mod.entity.boundingbox.IBoundingBox; import cam72cam.mod.math.Vec3d; import cam72cam.mod.model.obj.FaceAccessor; +import cam72cam.mod.model.obj.OBJFace; import cam72cam.mod.resource.Identifier; import cam72cam.mod.serialization.*; import cam72cam.mod.serialization.ResourceCache.GenericByteBuffer; @@ -592,6 +594,83 @@ public List getComponents(ModelComponentType name) { return renderComponents.get(name); } + public boolean hitsNavCollisionMesh(Gauge gauge, Vec3d passengerOffset, Vec3d movement) { + if (navMesh.collisionRoot == null) { + return false; + } + // Flip coords + passengerOffset = passengerOffset.rotateYaw(-90); + movement = movement.rotateYaw(-90); + + passengerOffset = passengerOffset.add(movement); + + IBoundingBox rayBox = IBoundingBox.from( + passengerOffset.subtract(0.25f, 0.5f, 0.25f), + passengerOffset.add(0.25f, 0.5f, 0.25f) + ); + List nearby = new ArrayList<>(); + navMesh.queryBVH(navMesh.collisionRoot, rayBox, nearby, gauge.scale()); + + Vec3d rayStart = passengerOffset.add(0, 1, 0); + Vec3d rayDir = movement.normalize(); + + for (OBJFace tri : nearby) { + Double t = MathUtil.intersectRayTriangle(rayStart, rayDir, tri); + if (t != null) { + return true; + } + } + + return false; + } + + public Vec3d correctPassengerBounds(Gauge gauge, Vec3d passengerOffset, boolean shouldSit) { + // Flip coords + passengerOffset = passengerOffset.rotateYaw(-90); + + for (float searchRange : new float[]{0.5f, 5f, 10f}) { + IBoundingBox rayBox = IBoundingBox.from( + passengerOffset.subtract(searchRange, searchRange, searchRange), + passengerOffset.add(searchRange, searchRange, searchRange) + ); + List nearby = new ArrayList<>(); + navMesh.queryBVH(navMesh.root, rayBox, nearby, gauge.scale()); + if (nearby.isEmpty()) { + continue; + } + + Vec3d closestPoint = null; + double closestDistanceSq = 0; + for (OBJFace face : nearby) { + Vec3d p0 = face.vertex0.pos; + Vec3d p1 = face.vertex1.pos; + Vec3d p2 = face.vertex2.pos; + + Vec3d pointOnTri = MathUtil.closestPointOnTriangle(passengerOffset, p0, p1, p2); + double distSq = passengerOffset.subtract(pointOnTri).lengthSquared(); + + if (closestPoint == null || distSq < closestDistanceSq) { + closestDistanceSq = distSq; + closestPoint = pointOnTri; + } + } + + // flip coords + return closestPoint.rotateYaw(90); + } + + // flip coords + return passengerOffset.rotateYaw(90); + } + + public boolean isAtFront(Gauge gauge, Vec3d pos) { + return pos.z >= this.passengerCompartmentLength * gauge.scale(); + } + + public boolean isAtRear(Gauge gauge, Vec3d pos) { + return pos.z <= -this.passengerCompartmentLength * gauge.scale(); + } + public List getItemComponents() { return itemComponents; }