diff --git a/src/main/java/cam72cam/immersiverailroading/entity/EntityRidableRollingStock.java b/src/main/java/cam72cam/immersiverailroading/entity/EntityRidableRollingStock.java index a73fefebf..d10f567a7 100644 --- a/src/main/java/cam72cam/immersiverailroading/entity/EntityRidableRollingStock.java +++ b/src/main/java/cam72cam/immersiverailroading/entity/EntityRidableRollingStock.java @@ -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; @@ -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); @@ -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 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 @@ -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.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()); } 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) @@ -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 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.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; + } + } + 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; + } + @Override public void onTick() { super.onTick(); diff --git a/src/main/java/cam72cam/immersiverailroading/floor/NavMesh.java b/src/main/java/cam72cam/immersiverailroading/floor/NavMesh.java new file mode 100644 index 000000000..6c47846af --- /dev/null +++ b/src/main/java/cam72cam/immersiverailroading/floor/NavMesh.java @@ -0,0 +1,184 @@ +package cam72cam.immersiverailroading.floor; + +import cam72cam.immersiverailroading.model.StockModel; +import cam72cam.immersiverailroading.registry.EntityRollingStockDefinition; +import cam72cam.immersiverailroading.util.VecUtil; +import cam72cam.mod.ModCore; +import cam72cam.mod.entity.boundingbox.IBoundingBox; +import cam72cam.mod.math.Vec3d; +import cam72cam.mod.math.Vec3i; +import cam72cam.mod.model.obj.FaceAccessor; +import cam72cam.mod.model.obj.OBJFace; +import cam72cam.mod.model.obj.Vec2f; +import cam72cam.mod.util.Axis; + +import java.util.*; + +public class NavMesh { + public BVHNode root; + public BVHNode collisionRoot; + private final boolean hasNavMesh; + // 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()); + } else { + try { + initLegacy(definition); + } catch (IllegalArgumentException e) { + ModCore.catching(e); + } + } + } + + private void initNavMesh(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); + sub.forEach(a -> floor.add(a.asOBJFace())); + }); + } + this.root = buildBVH(floor, 0); + + List collision = new ArrayList<>(); + if (model.collision != null) { + model.collision.groups().forEach(group -> { + FaceAccessor sub = accessor.getSubByGroup(group.name); + sub.forEach(a -> collision.add(a.asOBJFace())); + }); + } + this.collisionRoot = buildBVH(collision, 0); + } + + private void initLegacy(EntityRollingStockDefinition def) throws IllegalArgumentException { + 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())); + } + + if (center == null) { + center = Vec3d.ZERO; + } + + OBJFace face1 = new OBJFace(); + OBJFace face2 = new OBJFace(); + + Vec2f uv = new Vec2f(0, 0); + Vec3d normal = new Vec3d(0, 1, 0); + + Vec3d vertex1 = center.add(-length, 0, width / 2); + Vec3d vertex2 = center.add(length, 0, width / 2); + Vec3d vertex3 = center.add(length, 0, -width / 2); + Vec3d vertex4 = center.add(-length, 0, -width / 2); + + face1.vertex0 = new OBJFace.Vertex(vertex1, uv); + face1.vertex1 = new OBJFace.Vertex(vertex2, uv); + face1.vertex2 = new OBJFace.Vertex(vertex3, uv); + + face2.vertex0 = new OBJFace.Vertex(vertex1, uv); + face2.vertex1 = new OBJFace.Vertex(vertex3, uv); + face2.vertex2 = new OBJFace.Vertex(vertex4, uv); + + 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; + } + + public static class BVHNode { + IBoundingBox bounds; + BVHNode left; + BVHNode right; + List triangles; + + boolean isLeaf() { + return triangles != null; + } + } + + public BVHNode buildBVH(List triangles, int depth) { + if (triangles.size() <= LEAF_SIZE || depth > MAX_DEPTH) { + IBoundingBox bounds = IBoundingBox.from(Vec3i.ZERO); + for (OBJFace face : triangles) { + bounds = bounds.expandToFit(face.getBoundingBox()); + } + BVHNode node = new BVHNode(); + node.bounds = bounds; + node.triangles = triangles; + return node; + } + + IBoundingBox bounds = IBoundingBox.from(Vec3i.ZERO); + for (OBJFace face : triangles) { + bounds = bounds.expandToFit(face.getBoundingBox()); + } + + Vec3d size = bounds.max().subtract(bounds.min()); + Axis axis = (size.x > size.y && size.x > size.z) ? Axis.X : (size.y > size.z ? Axis.Y : Axis.Z); + + triangles.sort((a, b) -> Double.compare(getCentroid(a, axis), getCentroid(b, axis))); + int mid = triangles.size() / 2; + + BVHNode node = new BVHNode(); + node.left = buildBVH(triangles.subList(0, mid), depth + 1); + node.right = buildBVH(triangles.subList(mid, triangles.size()), depth + 1); + node.bounds = bounds; + return node; + } + + public void queryBVH(BVHNode node, IBoundingBox query, List result, double scale) { + query = unscaleBoxUniform(query, scale); + queryBVHInternal(node, query, result, scale); + } + + + + public void queryBVHInternal(BVHNode node, IBoundingBox query, List result, double scale) { + if (node == null) return; + if (!node.bounds.intersects(query)) return; + +// query = unscaleBoxUniform(query, scale); + + if (node.isLeaf()) { + for (OBJFace tri : node.triangles) { + if (tri.getBoundingBox().intersects(query)) { + result.add(tri.scale(scale)); + } + } + } else { + queryBVHInternal(node.left, query, result, scale); + queryBVHInternal(node.right, query, result, scale); + } + } + + private static IBoundingBox unscaleBoxUniform(IBoundingBox box, double s) { + Vec3d a = box.min().scale(1.0 / s); + Vec3d b = box.max().scale(1.0 / s); + + IBoundingBox out = IBoundingBox.from( + new Vec3d(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z)), + new Vec3d(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z)) + ); + return out; + } + + private double getCentroid(OBJFace tri, Axis axis) { + return (VecUtil.getByAxis(tri.vertex0.pos, axis) + VecUtil.getByAxis(tri.vertex1.pos, axis) + VecUtil.getByAxis(tri.vertex2.pos, axis)) / 3f; + } +} \ No newline at end of file diff --git a/src/main/java/cam72cam/immersiverailroading/library/ModelComponentType.java b/src/main/java/cam72cam/immersiverailroading/library/ModelComponentType.java index ae90b75be..943e50cf8 100644 --- a/src/main/java/cam72cam/immersiverailroading/library/ModelComponentType.java +++ b/src/main/java/cam72cam/immersiverailroading/library/ModelComponentType.java @@ -118,6 +118,10 @@ public enum ModelComponentType { BRAKE_PRESSURE_X("BRAKE_PRESSURE_#ID#"), COUPLED_X("COUPLED_#ID#"), + // Floor + FLOOR("FLOOR"), + COLLISION("COLLISION"), + // REST IMMERSIVERAILROADING_BASE_COMPONENT("IMMERSIVERAILROADING_BASE_COMPNOENT"), REMAINING(""), diff --git a/src/main/java/cam72cam/immersiverailroading/model/StockModel.java b/src/main/java/cam72cam/immersiverailroading/model/StockModel.java index 2894f2b54..766606e7a 100644 --- a/src/main/java/cam72cam/immersiverailroading/model/StockModel.java +++ b/src/main/java/cam72cam/immersiverailroading/model/StockModel.java @@ -42,6 +42,8 @@ public class StockModel> doors; protected final List> controls; protected final List> gauges; @@ -136,6 +138,10 @@ public StockModel(DEFINITION def) throws Exception { this.bogeyFront = Bogey.get(provider, front, unifiedBogies(), ModelPosition.FRONT); this.bogeyRear = Bogey.get(provider, rear, unifiedBogies(), ModelPosition.REAR); + // Parse Floor and Collision Meshes + this.floor = provider.parse(ModelComponentType.FLOOR); + this.collision = provider.parse(ModelComponentType.COLLISION); + parseComponents(provider, def); provider.parse(ModelComponentType.IMMERSIVERAILROADING_BASE_COMPONENT); this.remaining = provider.parse(ModelComponentType.REMAINING); diff --git a/src/main/java/cam72cam/immersiverailroading/registry/EntityRollingStockDefinition.java b/src/main/java/cam72cam/immersiverailroading/registry/EntityRollingStockDefinition.java index f21e98fc4..d6e5944bb 100644 --- a/src/main/java/cam72cam/immersiverailroading/registry/EntityRollingStockDefinition.java +++ b/src/main/java/cam72cam/immersiverailroading/registry/EntityRollingStockDefinition.java @@ -3,10 +3,9 @@ import cam72cam.immersiverailroading.Config; import cam72cam.immersiverailroading.ConfigSound; import cam72cam.immersiverailroading.ImmersiveRailroading; -import cam72cam.immersiverailroading.entity.EntityBuildableRollingStock; +import cam72cam.immersiverailroading.entity.*; import cam72cam.immersiverailroading.entity.EntityCoupleableRollingStock.CouplerType; -import cam72cam.immersiverailroading.entity.EntityMoveableRollingStock; -import cam72cam.immersiverailroading.entity.EntityRollingStock; +import cam72cam.immersiverailroading.floor.NavMesh; import cam72cam.immersiverailroading.util.*; import cam72cam.immersiverailroading.gui.overlay.GuiBuilder; import cam72cam.immersiverailroading.gui.overlay.Readouts; @@ -15,8 +14,7 @@ import cam72cam.immersiverailroading.model.components.ModelComponent; import cam72cam.mod.entity.EntityRegistry; import cam72cam.mod.math.Vec3d; -import cam72cam.mod.model.obj.OBJGroup; -import cam72cam.mod.model.obj.VertexBuffer; +import cam72cam.mod.model.obj.FaceAccessor; import cam72cam.mod.resource.Identifier; import cam72cam.mod.serialization.*; import cam72cam.mod.serialization.ResourceCache.GenericByteBuffer; @@ -29,8 +27,8 @@ import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.io.InputStream; + +import java.io.*; import java.util.*; import java.util.function.Function; import java.util.function.Supplier; @@ -67,7 +65,7 @@ public abstract class EntityRollingStockDefinition { public float darken; public Identifier modelLoc; protected StockModel model; - private Vec3d passengerCenter; + public Vec3d passengerCenter; private float bogeyFront; private float bogeyRear; private float couplerOffsetFront; @@ -79,15 +77,15 @@ public abstract class EntityRollingStockDefinition { private double rearBounds; private double heightBounds; private double widthBounds; - private double passengerCompartmentLength; - private double passengerCompartmentWidth; + public Double passengerCompartmentLength; + public Double passengerCompartmentWidth; private double weight; private int maxPassengers; private int snowLayers; private float interiorLightLevel; private boolean hasIndependentBrake; private boolean hasPressureBrake; - private final Map> renderComponents; + private final EnumMap> renderComponents; private final List itemComponents; private final Function heightmap; private final Map lights = new HashMap<>(); @@ -107,6 +105,8 @@ public abstract class EntityRollingStockDefinition { public Map cgDefaults; public Map widgetConfig; + public NavMesh navMesh; + public static class SoundDefinition { public final Identifier start; public final Identifier main; @@ -316,7 +316,9 @@ public EntityRollingStockDefinition(Class type, St this.model = createModel(); this.itemGroups = model.groups.keySet().stream().filter(x -> !ModelComponentType.shouldRender(x)).collect(Collectors.toList()); - this.renderComponents = new HashMap<>(); + this.navMesh = new NavMesh(this); + + this.renderComponents = new EnumMap<>(ModelComponentType.class); for (ModelComponent component : model.allComponents) { renderComponents.computeIfAbsent(component.type, v -> new ArrayList<>()) .add(0, component); @@ -448,16 +450,26 @@ public void loadData(DataBlock data) throws Exception { for (DataBlock alternate : alternates) { alternate.getValueMap().forEach((key, value) -> textureNames.put(value.asString(), key)); } - } catch (java.io.FileNotFoundException ex) { + } catch (FileNotFoundException ex) { ImmersiveRailroading.catching(ex); } modelLoc = data.getValue("model").asIdentifier(); DataBlock passenger = data.getBlock("passenger"); - passengerCenter = new Vec3d(0, passenger.getValue("center_y").asDouble() - 0.35, passenger.getValue("center_x").asDouble()).scale(internal_model_scale); - passengerCompartmentLength = passenger.getValue("length").asDouble() * internal_model_scale; - passengerCompartmentWidth = passenger.getValue("width").asDouble() * internal_model_scale; + + if (passenger.getValue("center_x") != null && passenger.getValue("center_y") != null) { + passengerCenter = new Vec3d(-passenger.getValue("center_x").asDouble(), passenger.getValue("center_y").asDouble() - 0.35, 0).scale(internal_model_scale); + } + + if (passenger.getValue("length") != null) { + passengerCompartmentLength = passenger.getValue("length").asDouble() * internal_model_scale; + } + + if (passenger.getValue("width") != null) { + passengerCompartmentWidth = passenger.getValue("width").asDouble() * internal_model_scale; + } + maxPassengers = passenger.getValue("slots").asInteger(); shouldSit = passenger.getValue("should_sit").asBoolean(); @@ -580,37 +592,6 @@ public List getComponents(ModelComponentType name) { return renderComponents.get(name); } - public Vec3d correctPassengerBounds(Gauge gauge, Vec3d pos, boolean shouldSit) { - double gs = gauge.scale(); - Vec3d passengerCenter = this.passengerCenter.scale(gs); - pos = pos.subtract(passengerCenter); - if (pos.z > this.passengerCompartmentLength * gs) { - pos = new Vec3d(pos.x, pos.y, this.passengerCompartmentLength * gs); - } - - if (pos.z < -this.passengerCompartmentLength * gs) { - pos = new Vec3d(pos.x, pos.y, -this.passengerCompartmentLength * gs); - } - - if (Math.abs(pos.x) > this.passengerCompartmentWidth / 2 * gs) { - pos = new Vec3d(Math.copySign(this.passengerCompartmentWidth / 2 * gs, pos.x), pos.y, pos.z); - } - - pos = new Vec3d(pos.x, passengerCenter.y - (shouldSit ? 0.75 : 0), pos.z + passengerCenter.z); - - return pos; - } - - public boolean isAtFront(Gauge gauge, Vec3d pos) { - pos = pos.subtract(passengerCenter.scale(gauge.scale())); - return pos.z >= this.passengerCompartmentLength * gauge.scale(); - } - - public boolean isAtRear(Gauge gauge, Vec3d pos) { - pos = pos.subtract(passengerCenter.scale(gauge.scale())); - return pos.z <= -this.passengerCompartmentLength * gauge.scale(); - } - public List getItemComponents() { return itemComponents; } @@ -675,33 +656,31 @@ private static class HeightMapData { .collect(Collectors.toList()); data = new float[components.size() * xRes * zRes]; - VertexBuffer vb = def.model.vbo.buffer.get(); + FaceAccessor visitor = def.model.getFaceAccessor(); for (int i = 0; i < components.size(); i++) { ModelComponent rc = components.get(i); int idx = i * xRes * zRes; for (String group : rc.modelIDs) { - OBJGroup faces = def.model.groups.get(group); + FaceAccessor grouped = visitor.getSubByGroup(group); - for (int face = faces.faceStart; face <= faces.faceStop; face++) { + for (FaceAccessor face : grouped) { Path2D path = new Path2D.Float(); - float fheight = 0; - boolean first = true; - for (int point = 0; point < vb.vertsPerFace; point++) { - int vertex = face * vb.vertsPerFace * vb.stride + point * vb.stride; - float vertX = vb.data[vertex + 0]; - float vertY = vb.data[vertex + 1]; - float vertZ = vb.data[vertex + 2]; - vertX += def.frontBounds; - vertZ += def.widthBounds / 2; - if (first) { - path.moveTo(vertX * ratio, vertZ * ratio); - first = false; - } else { - path.lineTo(vertX * ratio, vertZ * ratio); - } - fheight += vertY / vb.vertsPerFace; - } + float faceHeight = 0; + + double v0x = (face.v0.x() + def.frontBounds) * ratio; + double v0z = (face.v0.z() + def.widthBounds / 2) * ratio; + double v1x = (face.v1.x() + def.frontBounds) * ratio; + double v1z = (face.v1.z() + def.widthBounds / 2) * ratio; + double v2x = (face.v2.x() + def.frontBounds) * ratio; + double v2z = (face.v2.z() + def.widthBounds / 2) * ratio; + + path.moveTo(v0x, v0z); + path.lineTo(v1x, v1z); + path.lineTo(v2x, v2z); + + faceHeight = faceHeight + (face.v0.y() + face.v1.y() + face.v2.y()) / 3; + Rectangle2D bounds = path.getBounds2D(); if (bounds.getWidth() * bounds.getHeight() < 1) { continue; @@ -711,7 +690,7 @@ private static class HeightMapData { float relX = ((xRes - 1) - x); float relZ = z; if (bounds.contains(relX, relZ) && path.contains(relX, relZ)) { - float relHeight = fheight / (float) def.heightBounds; + float relHeight = faceHeight / (float) def.heightBounds; relHeight = ((int) Math.ceil(relHeight * precision)) / (float) precision; data[idx + x * zRes + z] = Math.max(data[idx + x * zRes + z], relHeight); } @@ -921,5 +900,4 @@ public double getBrakeShoeFriction() { public int getSnowLayers() { return snowLayers; } - } diff --git a/src/main/java/cam72cam/immersiverailroading/util/MathUtil.java b/src/main/java/cam72cam/immersiverailroading/util/MathUtil.java index 8a5b84c4b..e44035f59 100644 --- a/src/main/java/cam72cam/immersiverailroading/util/MathUtil.java +++ b/src/main/java/cam72cam/immersiverailroading/util/MathUtil.java @@ -1,5 +1,10 @@ package cam72cam.immersiverailroading.util; +import cam72cam.mod.math.Vec3d; +import cam72cam.mod.model.obj.OBJFace; + +import java.util.List; + public class MathUtil { public static double gradeToRadians(double grade) { return Math.atan2(grade, 100); @@ -61,4 +66,75 @@ public static float clamp(float val, float min, float max) { public static double clamp(double val, double min, double max) { return Math.max(min, Math.min(max, val)); } + + public static Double intersectRayTriangle(Vec3d rayOrigin, Vec3d rayDir, OBJFace face) { + final float EPSILON = 1e-6f; + + Vec3d edge1 = face.vertex1.pos.subtract(face.vertex0.pos); + Vec3d edge2 = face.vertex2.pos.subtract(face.vertex0.pos); + + Vec3d h = rayDir.crossProduct(edge2); + double a = edge1.dotProduct(h); + + if (Math.abs(a) < EPSILON) return null; + + double f = 1.0f / a; + Vec3d s = rayOrigin.subtract(face.vertex0.pos); + double u = f * s.dotProduct(h); + + if (u < 0.0f || u > 1.0f) return null; + + Vec3d q = s.crossProduct(edge1); + double v = f * rayDir.dotProduct(q); + + if (v < 0.0f || u + v > 1.0f) return null; + + double t = f * edge2.dotProduct(q); + + return t >= 0 ? t : null; + } + + public static Vec3d closestPointOnTriangle(Vec3d p, Vec3d p0, Vec3d p1, Vec3d p2) { + Vec3d ab = p1.subtract(p0); + Vec3d ac = p2.subtract(p0); + Vec3d ap = p.subtract(p0); + double d1 = ab.dotProduct(ap); + double d2 = ac.dotProduct(ap); + + if (d1 <= 0f && d2 <= 0f) return p0; + + Vec3d bp = p.subtract(p1); + double d3 = ab.dotProduct(bp); + double d4 = ac.dotProduct(bp); + if (d3 >= 0f && d4 <= d3) return p1; + + double vc = d1 * d4 - d3 * d2; + if (vc <= 0f && d1 >= 0f && d3 <= 0f) { + double v = d1 / (d1 - d3); + return p0.add(ab.scale(v)); + } + + Vec3d cp = p.subtract(p2); + double d5 = ab.dotProduct(cp); + double d6 = ac.dotProduct(cp); + if (d6 >= 0f && d5 <= d6) return p2; + + double vb = d5 * d2 - d1 * d6; + if (vb <= 0f && d2 >= 0f && d6 <= 0f) { + double w = d2 / (d2 -d6); + return p0.add(ac.scale(w)); + } + + double va = d3 * d6 -d5 * d4; + Vec3d bc = p2.subtract(p1); + if (va <= 0f && (d4 - d3) >= 0.0 && (d5 - d6) >= 0f) { + double w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + return p1.add(bc.scale(w)); + } + + double denom = 1f / (va + vb + vc); + double v = vb * denom; + double w = vc * denom; + return p0.add(ab.scale(v)).add(ac.scale(w)); + } } diff --git a/src/main/java/cam72cam/immersiverailroading/util/VecUtil.java b/src/main/java/cam72cam/immersiverailroading/util/VecUtil.java index c34b312bc..6623cf7ae 100644 --- a/src/main/java/cam72cam/immersiverailroading/util/VecUtil.java +++ b/src/main/java/cam72cam/immersiverailroading/util/VecUtil.java @@ -1,8 +1,8 @@ package cam72cam.immersiverailroading.util; import cam72cam.mod.math.Vec3d; +import cam72cam.mod.util.Axis; import cam72cam.mod.util.FastMath; -import util.Matrix4; public class VecUtil { private VecUtil() { @@ -30,11 +30,16 @@ public static Vec3d rotateYaw(Vec3d pos, float rotationYaw) { ); } public static Vec3d rotatePitch(Vec3d pos, float rotationPitch) { - if (Math.abs(rotationPitch) < 0.01) { + if (Math.abs(rotationPitch) == 0) { return pos; } - // TODO optimize me! - return new Matrix4().rotate(Math.toRadians(rotationPitch), 0, 0, 1).apply(pos); + //return new Matrix4().rotate(Math.toRadians(rotationPitch), 0, 0, 1).apply(pos); + double rad = Math.toRadians(rotationPitch); + double cos = Math.cos(rad); + double sin = Math.sin(rad); + return new Vec3d(pos.x, + pos.y * cos + pos.z * sin, + pos.z * cos - pos.y * sin); } public static Vec3d fromWrongYaw(double distance, float yaw) { @@ -61,4 +66,13 @@ public static Vec3d fromWrongYawPitch(float distance, float rotationYaw, float r public static Vec3d between(Vec3d front, Vec3d rear) { return new Vec3d((front.x + rear.x) / 2, (front.y + rear.y) / 2, (front.z + rear.z) / 2); } + + public static double getByAxis(Vec3d vec, Axis axis) { + switch (axis) { + case X: return vec.x; + case Y: return vec.y; + case Z: return vec.z; + default: throw new IllegalArgumentException("Invalid axis, did you provide a null?"); + } + } }