Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fb625cc
[feat] Added multi-floor possibility by defining a FLOOR object
poizzy Sep 5, 2025
690e4ff
Merge branch 'master' into multi-level-floors
poizzy Sep 5, 2025
e1bbdfe
[cleanup] Removed accidental override of equals and hash in SoundDefi…
poizzy Sep 6, 2025
1e6933a
[fix] Moved buffer supplier out of loop
poizzy Sep 7, 2025
0cde08b
[fix] Re-added commented out intersects check | Removed magic numbers
poizzy Sep 7, 2025
cf18836
[other] Integrated Mesh and CollisionBox into UMC
poizzy Sep 9, 2025
b684d57
[feat] Integrated `FLOOR` and `COLLISION` into the ModelComponentSystem
poizzy Sep 9, 2025
93e5a0c
feat: switch to #a97352f
Goldenfield192 Sep 27, 2025
f40b28d
feat: switch to new geometry API (a97352f)
Goldenfield192 Sep 27, 2025
ad6fe1d
[fix] Faster and simpler 'closestPointOnTriangle' algorithm
poizzy Sep 27, 2025
08a4612
[Refactor] Removed VecUtil.dotProduct() and VecUtil.crossProduct()
poizzy Sep 27, 2025
3273d43
[chore] Moved utility Methods out of EntityCustomPlayerMovement.java
poizzy Jan 18, 2026
b50987b
[chore] renamed getCenter() to center()
poizzy Jan 18, 2026
450e794
[feat] Implemented old way of defining the floor into the new System
poizzy Jan 18, 2026
ecc34a5
Merge branch 'master-merge' into multi-floor-feature
Feb 24, 2026
995921d
[fix] Changed passengerCenter to x-coordinate system
Feb 24, 2026
f4fabbb
[chore] Removed Nullable annotations from EntityRollingStockDefinition
Feb 24, 2026
3723def
[feat] Made "center_x" and "center_y" optional
Feb 24, 2026
4b2ec3d
ref: merge removePitch/reapplyPitch into rotatePitch (#14)
Goldenfield192 Feb 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
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;
Expand Down Expand Up @@ -58,7 +63,7 @@ public ClickResult onClick(Player player, Player.Hand hand) {
}
}

private Vec3d getSeatPosition(UUID passenger) {
protected Vec3d getSeatPosition(UUID passenger) {
String seat = seatedPassengers.entrySet().stream()
.filter(x -> x.getValue().equals(passenger))
.map(Map.Entry::getKey).findFirst().orElse(null);
Expand All @@ -70,29 +75,45 @@ private Vec3d getSeatPosition(UUID passenger) {

@Override
public Vec3d getMountOffset(Entity passenger, Vec3d off) {
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;
}
}
}
NavMesh navMesh = getDefinition().navMesh;

off = off.scale(gauge.scale());
Vec3d seat = getSeatPosition(passenger.getUUID());
if (seat != null) {
return seat;
}

int wiggle = passenger.isVillager() ? 10 : 0;
off = off.add((Math.random()-0.5) * wiggle, 0, (Math.random()-0.5) * wiggle);
off = this.getDefinition().correctPassengerBounds(gauge, off, shouldRiderSit(passenger));
Vec3d realOffset = off.rotateYaw(-90);
IBoundingBox queryBox = IBoundingBox.from(
realOffset.subtract(4f, 4f, 4f),
realOffset.add(4f, 4f, 4f)
);

List<OBJFace> 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;
}
}

return off;
if (closestPoint != null) {
return closestPoint.rotateYaw(90);
} else {
return new Vec3d(0, 0, 0);
}
}

@Override
Expand All @@ -111,22 +132,53 @@ public boolean shouldRiderSit(Entity passenger) {

@Override
public Vec3d onPassengerUpdate(Entity passenger, Vec3d offset) {
Vec3d movement = new Vec3d(0, 0, 0);
if (passenger.isPlayer()) {
offset = playerMovement(passenger.asPlayer(), offset);
movement = playerMovement(passenger.asPlayer(), offset);
}
Vec3d targetXZ = VecUtil.removePitch(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<OBJFace> 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.reapplyPitch(new Vec3d(targetXZ.x, closestY, targetXZ.z), this.getRotationPitch());
}

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;
}

private boolean isNearestDoorOpen(Player source) {
protected 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)
Expand All @@ -136,63 +188,140 @@ private boolean isNearestDoorOpen(Player source) {
.isPresent();
}

private Vec3d playerMovement(Player source, Vec3d offset) {
protected Vec3d playerMovement(Player source, Vec3d offset) {
Vec3d movement = source.getMovementInput();
/*
if (sprinting) {
movement = movement.scale(3);
}
*/
if (movement.length() < 0.1) {
return offset;
}
if (movement.length() <= 0.1) {
return offset;
}

movement = new Vec3d(movement.x, 0, movement.z).rotateYaw(this.getRotationYaw() - source.getRotationYawHead());
movement = new Vec3d(movement.x, 0, movement.z).rotateYaw(this.getRotationYaw() - source.getRotationYawHead());
Vec3d localOffset = offset.rotateYaw(-90).add(movement.rotateYaw(-90));

offset = offset.add(movement);
IBoundingBox rayBox = IBoundingBox.from(
localOffset.subtract(0.2f, 0.2f, 0.2f),
localOffset.add(0.2f, 0.2f, 0.2f)
);
List<OBJFace> 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();

boolean atFront = this.getDefinition().isAtFront(gauge, offset);
boolean atBack = this.getDefinition().isAtRear(gauge, offset);
// TODO config for strict doors
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 atDoor = isNearestDoorOpen(source);

atFront &= atDoor;
atBack &= atDoor;
isAtFront &= atDoor;
isAtBack &= atDoor;

for (CouplerType coupler : CouplerType.values()) {
boolean atCoupler = coupler == CouplerType.FRONT ? atFront : atBack;
if (atCoupler && couplable.isCoupled(coupler)) {
for (EntityCoupleableRollingStock.CouplerType coupler : EntityCoupleableRollingStock.CouplerType.values()) {
boolean atCoupler = coupler == EntityCoupleableRollingStock.CouplerType.FRONT ? isAtFront : isAtBack;
if (atCoupler && coupleable.isCoupled(coupler)) {
EntityCoupleableRollingStock coupled = ((EntityCoupleableRollingStock) this).getCoupled(coupler);
if (coupled != null) {
if (((EntityRidableRollingStock)coupled).isNearestDoorOpen(source)) {
if (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(),
couplable.getCoupledUUID(coupler),
couplable.getCoupledUUID(coupler)
coupleable.getCoupledUUID(coupler),
coupleable.getCoupledUUID(coupler)
);
}
return offset;
}
}
}

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 offset;
}

private boolean isDoorOpen(Vec3d start, Vec3d end) {
start = VecUtil.removePitch(start, this.getRotationPitch());
end = VecUtil.removePitch(end, this.getRotationPitch());

start = start.rotateYaw(-90);
end = start.add(end.rotateYaw(-90));

List<Door<?>> 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;
}
}
return intersects;
}

private boolean isAtCoupler(Vec3d offset, Vec3d movement, EntityCoupleableRollingStock.CouplerType type) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem to work based on my initial testing. I've got two pieces of stock on a curve and it does not detect isAtFront or isAtBack correctly.

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<OBJFace> 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;
}

@Override
public void onTick() {
super.onTick();
Expand Down
Loading