Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84b3d9723e | |||
| 7ead63957c | |||
| 4652f58415 | |||
| 75021b3360 | |||
| fec526c2c1 | |||
| b4e6c8c794 |
16
CHANGELOG.md
Normal file
16
CHANGELOG.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
## [001] - 2025-08-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Multi-threaded calculation
|
||||||
19
README.md
Normal file
19
README.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Kepler
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Simplest arrow trajectory visualizer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
No dependency. This is the independent mod.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Copy the JAR to `mods` folder.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Pull your bowstring!
|
||||||
BIN
docs/image1.png
Normal file
BIN
docs/image1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 316 KiB |
@@ -8,10 +8,18 @@ maven_group=org.walruslab
|
|||||||
archives_base_name=kepler
|
archives_base_name=kepler
|
||||||
# Dependencies
|
# Dependencies
|
||||||
# check this on https://modmuss50.me/fabric.html
|
# check this on https://modmuss50.me/fabric.html
|
||||||
minecraft_version=1.21.7
|
#minecraft_version=1.21.7
|
||||||
yarn_mappings=1.21.7+build.8
|
#yarn_mappings=1.21.7+build.8
|
||||||
loader_version=0.16.14
|
#loader_version=0.16.14
|
||||||
|
#loom_version=1.11-SNAPSHOT
|
||||||
|
#
|
||||||
|
## Fabric API
|
||||||
|
#fabric_version=0.129.0+1.21.7
|
||||||
|
|
||||||
|
minecraft_version=1.21.8
|
||||||
|
yarn_mappings=1.21.8+build.1
|
||||||
|
loader_version=0.17.2
|
||||||
loom_version=1.11-SNAPSHOT
|
loom_version=1.11-SNAPSHOT
|
||||||
|
|
||||||
# Fabric API
|
# Fabric API
|
||||||
fabric_version=0.129.0+1.21.7
|
fabric_version=0.131.0+1.21.8
|
||||||
@@ -3,6 +3,7 @@ package org.walruslab.kepler;
|
|||||||
import net.fabricmc.api.ModInitializer;
|
import net.fabricmc.api.ModInitializer;
|
||||||
|
|
||||||
public class Kepler implements ModInitializer {
|
public class Kepler implements ModInitializer {
|
||||||
|
public static final String MOD_ID = "kepler";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
|
|||||||
@@ -2,16 +2,19 @@ package org.walruslab.kepler.client;
|
|||||||
|
|
||||||
import net.fabricmc.api.ClientModInitializer;
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import org.walruslab.kepler.render.TrajectoryRenderer;
|
import org.walruslab.kepler.rhythm.hud.render.RhythmHudRenderer;
|
||||||
|
import org.walruslab.kepler.trajectory.TrajectoryRenderer;
|
||||||
|
|
||||||
public class KeplerClient implements ClientModInitializer {
|
public class KeplerClient implements ClientModInitializer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeClient() {
|
public void onInitializeClient() {
|
||||||
|
var renderer = new TrajectoryRenderer();
|
||||||
|
// var hudRenderer = new RhythmHudRenderer();
|
||||||
|
// hudRenderer.setupRenderer();
|
||||||
ClientEntityEvents.ENTITY_LOAD.register((entity, _a) -> {
|
ClientEntityEvents.ENTITY_LOAD.register((entity, _a) -> {
|
||||||
if (entity instanceof PlayerEntity) TrajectoryRenderer.setupRenderLine((PlayerEntity) entity);
|
if (entity instanceof PlayerEntity) renderer.setupRenderLine((PlayerEntity) entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
package org.walruslab.kepler.render;
|
|
||||||
|
|
||||||
import com.mojang.blaze3d.opengl.GlStateManager;
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
|
||||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
|
||||||
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
|
|
||||||
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
|
|
||||||
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
|
|
||||||
import net.jpountz.util.Utils;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
import net.minecraft.client.render.Tessellator;
|
|
||||||
import net.minecraft.client.render.VertexFormats;
|
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
|
||||||
import net.minecraft.entity.projectile.ArrowEntity;
|
|
||||||
import net.minecraft.item.BowItem;
|
|
||||||
import net.minecraft.item.Items;
|
|
||||||
import net.minecraft.predicate.entity.EntityPredicate;
|
|
||||||
import net.minecraft.predicate.entity.EntityPredicates;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import net.minecraft.util.TypeFilter;
|
|
||||||
import net.minecraft.util.Util;
|
|
||||||
import net.minecraft.util.math.Box;
|
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
import net.minecraft.util.math.Vec3i;
|
|
||||||
import org.joml.Vector3d;
|
|
||||||
import org.joml.Vector3f;
|
|
||||||
import org.lwjgl.opengl.GL11;
|
|
||||||
import org.walruslab.kepler.trajectory.Trajectory;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public class TrajectoryRenderer {
|
|
||||||
public static void setupRenderLine(PlayerEntity player) {
|
|
||||||
player.sendMessage(Text.of("Kepler: TrajectoryRenderer setupRenderLine"), false);
|
|
||||||
WorldRenderEvents.END.register((drawContext) -> {
|
|
||||||
// get all error entity in player world
|
|
||||||
var world = player.getWorld();
|
|
||||||
var arrows = world.getEntitiesByType(TypeFilter.instanceOf(ArrowEntity.class), new Box(player.getPos().subtract(Vec3d.of(new Vec3i(100, 100, 100))), player.getPos().add(Vec3d.of(new Vec3i(100, 100, 100)))), EntityPredicates.VALID_ENTITY);
|
|
||||||
|
|
||||||
for (ArrowEntity arrow : arrows) {
|
|
||||||
if (arrow.groundCollision) return;
|
|
||||||
|
|
||||||
var pos = arrow.getPos();
|
|
||||||
var velocity = arrow.getVelocity();
|
|
||||||
|
|
||||||
var tr = new Trajectory(pos.toVector3f(), velocity.toVector3f());
|
|
||||||
var hit = tr.getTrajectoryPoints(world, 0.1f, player);
|
|
||||||
var points = hit.points();
|
|
||||||
|
|
||||||
if (points.size() < 2) {
|
|
||||||
continue; // Skip if not enough points
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the trajectory line
|
|
||||||
if (hit.hitEntity()) {
|
|
||||||
renderLine(drawContext, points, 0.0f, 1.0f, 0.0f); // Red for hit
|
|
||||||
} else {
|
|
||||||
renderLine(drawContext, points, 1.0f, 0.0f, 1.0f); // Green for no hit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var livingEntities = world.getOtherEntities(player, new Box(player.getPos().subtract(Vec3d.of(new Vec3i(100, 100, 100))), player.getPos().add(Vec3d.of(new Vec3i(100, 100, 100)))), EntityPredicates.VALID_ENTITY);
|
|
||||||
for (var entity : livingEntities) {
|
|
||||||
if (entity instanceof ArrowEntity) continue;
|
|
||||||
|
|
||||||
var pos = entity.getPos();
|
|
||||||
var velocity = entity.getVelocity();
|
|
||||||
|
|
||||||
if (velocity.length() < 0.1) continue; // Skip if velocity is too low
|
|
||||||
|
|
||||||
var predictedPos = new Vector3f(
|
|
||||||
(float) (entity.getX() + velocity.x * 20),
|
|
||||||
(float) (entity.getY() + velocity.y * 20),
|
|
||||||
(float) (entity.getZ() + velocity.z * 20)
|
|
||||||
);
|
|
||||||
|
|
||||||
var points = List.of(
|
|
||||||
new Vector3f((float) pos.x, (float) pos.y, (float) pos.z),
|
|
||||||
predictedPos
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render the trajectory line
|
|
||||||
renderLine(drawContext, points, 0.0f, 0.0f, 1.0f); // Blue for entity
|
|
||||||
}
|
|
||||||
|
|
||||||
// check player is shooting bow
|
|
||||||
var activeItem = player.getMainHandStack();
|
|
||||||
if (activeItem != null && (activeItem.isOf(Items.BOW) || activeItem.isOf(Items.CROSSBOW))) {
|
|
||||||
float speed;
|
|
||||||
if (activeItem.isOf(Items.BOW)) {
|
|
||||||
var item = (BowItem) activeItem.getItem();
|
|
||||||
var f = BowItem.getPullProgress(player.getItemUseTime());
|
|
||||||
speed = f * 3.0f;
|
|
||||||
} else {
|
|
||||||
speed = 3.15f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dirDouble = player.getRotationVec(1.0f);
|
|
||||||
|
|
||||||
var dir = new Vector3f(
|
|
||||||
(float) dirDouble.x,
|
|
||||||
(float) dirDouble.y,
|
|
||||||
(float) dirDouble.z
|
|
||||||
).normalize();
|
|
||||||
|
|
||||||
var pos = player.getEyePos();
|
|
||||||
var playerVelocity = player.getVelocity();
|
|
||||||
|
|
||||||
var velocity = new Vector3f(
|
|
||||||
(float) (dir.x * speed + playerVelocity.x),
|
|
||||||
(float) (dir.y * speed + playerVelocity.y),
|
|
||||||
(float) (dir.z * speed + playerVelocity.z)
|
|
||||||
);
|
|
||||||
|
|
||||||
var tr = new Trajectory(pos.toVector3f(), velocity);
|
|
||||||
var hit = tr.getTrajectoryPoints(world, 0.1f, player);
|
|
||||||
|
|
||||||
var points = hit.points();
|
|
||||||
|
|
||||||
if (points.size() >= 2) {
|
|
||||||
var playerRight = dir.cross(new Vector3f(0, 1, 0)).normalize();
|
|
||||||
// move points to the right, closest point -> move 1, farthest point -> move 0, linear
|
|
||||||
for (int i = 0; i < points.size(); i++) {
|
|
||||||
var point = points.get(i);
|
|
||||||
var rightOffset = 0.05;
|
|
||||||
var dx = (float) (point.x + playerRight.x * rightOffset * (1 - ((float) i / points.size())));
|
|
||||||
var dy = (float) (point.y + playerRight.y * rightOffset * (1 - ((float) i / points.size())));
|
|
||||||
var dz = (float) (point.z + playerRight.z * rightOffset * (1 - ((float) i / points.size())));
|
|
||||||
|
|
||||||
points.set(i, new Vector3f(dx, dy, dz));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the trajectory line
|
|
||||||
if (hit.hitEntity()) {
|
|
||||||
renderLine(drawContext, points, 0.0f, 1.0f, 0.0f); // Red for hit
|
|
||||||
} else {
|
|
||||||
renderLine(drawContext, points, 1.0f, 0.0f, 1.0f); // Green for no hit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void renderLine(WorldRenderContext drawContext, List<Vector3f> points, float r, float g, float b) {
|
|
||||||
// enable z-buffer
|
|
||||||
GlStateManager._enableDepthTest();
|
|
||||||
GlStateManager._depthFunc(GL11.GL_LEQUAL);
|
|
||||||
GlStateManager._enableBlend();
|
|
||||||
|
|
||||||
var tesselator = Tessellator.getInstance();
|
|
||||||
var buf = tesselator.begin(VertexFormat.DrawMode.DEBUG_LINE_STRIP, VertexFormats.POSITION_COLOR);
|
|
||||||
var mat = drawContext.positionMatrix();
|
|
||||||
|
|
||||||
var playerPos = MinecraftClient.getInstance().getCameraEntity().getCameraPosVec(0);
|
|
||||||
|
|
||||||
for (var point: points) {
|
|
||||||
float dx = (float) (point.x - playerPos.x);
|
|
||||||
float dy = (float) (point.y - playerPos.y);
|
|
||||||
float dz = (float) (point.z - playerPos.z);
|
|
||||||
|
|
||||||
buf.vertex(mat, dx, dy, dz);
|
|
||||||
buf.color(r, g, b, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderLayer.getDebugLineStrip(1).draw(buf.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.walruslab.kepler.rhythm.hud.model;
|
||||||
|
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
public record RhythmHudNote(
|
||||||
|
Vector3f color,
|
||||||
|
float angle,
|
||||||
|
float distance,
|
||||||
|
float noteStart,
|
||||||
|
float noteEnd
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.walruslab.kepler.rhythm.hud.render;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||||
|
import net.minecraft.client.gl.RenderPipelines;
|
||||||
|
import net.minecraft.client.gui.ScreenRect;
|
||||||
|
import net.minecraft.client.gui.render.state.SimpleGuiElementRenderState;
|
||||||
|
import net.minecraft.client.render.VertexConsumer;
|
||||||
|
import net.minecraft.client.texture.TextureSetup;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.joml.Matrix3x2fStack;
|
||||||
|
import org.joml.Vector2f;
|
||||||
|
import org.joml.Vector4f;
|
||||||
|
|
||||||
|
public record GuiScreenQuad(ScreenRect scissorArea, Matrix3x2fStack pose, Vector2f v1, Vector2f v2, Vector2f v3, Vector2f v4, Vector4f color) implements SimpleGuiElementRenderState {
|
||||||
|
@Override
|
||||||
|
public void setupVertices(VertexConsumer vertices, float depth) {
|
||||||
|
vertices.vertex(pose, v1.x, v1.y, depth).color(color.x, color.y, color.z, color.w);
|
||||||
|
vertices.vertex(pose, v2.x, v2.y, depth).color(color.x, color.y, color.z, color.w);
|
||||||
|
vertices.vertex(pose, v3.x, v3.y, depth).color(color.x, color.y, color.z, color.w);
|
||||||
|
vertices.vertex(pose, v4.x, v4.y, depth).color(color.x, color.y, color.z, color.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderPipeline pipeline() {
|
||||||
|
return RenderPipelines.GUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextureSetup textureSetup() {
|
||||||
|
return TextureSetup.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ScreenRect bounds() {
|
||||||
|
var minX = (int) Math.min(Math.min(v1.x, v2.x), Math.min(v3.x, v4.x));
|
||||||
|
var maxX = (int) Math.max(Math.max(v1.x, v2.x), Math.max(v3.x, v4.x));
|
||||||
|
|
||||||
|
var minY = (int) Math.min(Math.min(v1.y, v2.y), Math.min(v3.y, v4.y));
|
||||||
|
var maxY = (int) Math.max(Math.max(v1.y, v2.y), Math.max(v3.y, v4.y));
|
||||||
|
|
||||||
|
return new ScreenRect(minX, minY, maxX - minX, maxY - minY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package org.walruslab.kepler.rhythm.hud.render;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||||
|
import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry;
|
||||||
|
import net.minecraft.client.gl.RenderPipelines;
|
||||||
|
import net.minecraft.client.gui.DrawContext;
|
||||||
|
import net.minecraft.client.gui.ScreenRect;
|
||||||
|
import net.minecraft.client.gui.render.state.SimpleGuiElementRenderState;
|
||||||
|
import net.minecraft.client.render.*;
|
||||||
|
import net.minecraft.client.texture.TextureSetup;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.walruslab.kepler.Kepler;
|
||||||
|
import org.walruslab.kepler.rhythm.hud.model.RhythmHudNote;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RhythmHudRenderer {
|
||||||
|
public void setupRenderer() {
|
||||||
|
HudElementRegistry.addLast(Identifier.of(Kepler.MOD_ID, "minco_hud"), this::handleRenderCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRenderCallback(DrawContext drawContext, RenderTickCounter tickCounter) {
|
||||||
|
System.out.println("asdf");
|
||||||
|
var mats = drawContext.getMatrices();
|
||||||
|
|
||||||
|
drawContext.state.addSimpleElement(new SimpleGuiElementRenderState() {
|
||||||
|
@Override
|
||||||
|
public @Nullable ScreenRect bounds() {
|
||||||
|
return new ScreenRect(10, 10, 50, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupVertices(VertexConsumer vertices, float depth) {
|
||||||
|
vertices.vertex(mats, 20.0f, 10.0f, depth).color(1.0f, 1.0f, 1.0f, 0.5f);
|
||||||
|
vertices.vertex(mats, 10.0f, 20.0f, depth).color(1.0f, 1.0f, 1.0f, 0.5f);
|
||||||
|
vertices.vertex(mats, 20.0f, 30.0f, depth).color(1.0f, 1.0f, 1.0f, 0.5f);
|
||||||
|
vertices.vertex(mats, 30.0f, 20.0f, depth).color(1.0f, 1.0f, 1.0f, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderPipeline pipeline() {
|
||||||
|
return RenderPipelines.GUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextureSetup textureSetup() {
|
||||||
|
return TextureSetup.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ScreenRect scissorArea() {
|
||||||
|
return drawContext.scissorStack.peekLast();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderHud(List<RhythmHudNote> state) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.walruslab.kepler.trajectory;
|
||||||
|
|
||||||
|
import net.minecraft.entity.projectile.ArrowEntity;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class ParallelTrajectoryManager {
|
||||||
|
private boolean isDataAvailable = false;
|
||||||
|
private List<TrajectoryHit> lastHits;
|
||||||
|
|
||||||
|
public void calculateTrajectories(List<ArrowEntity> arrows) {
|
||||||
|
if (!isDataAvailable) {
|
||||||
|
AtomicInteger i = new AtomicInteger();
|
||||||
|
lastHits = new ArrayList<>();
|
||||||
|
for (var arrow : arrows) {
|
||||||
|
var t = new Thread(() -> {
|
||||||
|
var hit = calculateArrow(arrow);
|
||||||
|
if (hit != null) {
|
||||||
|
lastHits.add(hit);
|
||||||
|
}
|
||||||
|
i.addAndGet(1);
|
||||||
|
|
||||||
|
if (arrows.size() == i.get()) {
|
||||||
|
isDataAvailable = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TrajectoryHit> getLastHits() {
|
||||||
|
if (isDataAvailable) {
|
||||||
|
isDataAvailable = false;
|
||||||
|
return lastHits;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable TrajectoryHit calculateArrow(ArrowEntity arrow) {
|
||||||
|
if (arrow.groundCollision) return null;
|
||||||
|
|
||||||
|
var pos = arrow.getPos();
|
||||||
|
var velocity = arrow.getVelocity();
|
||||||
|
|
||||||
|
var tr = new Trajectory(pos.toVector3f(), velocity.toVector3f());
|
||||||
|
var hit = tr.getTrajectoryPoints(arrow.getWorld(), 0.1f, arrow.getOwner());
|
||||||
|
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -53,21 +53,11 @@ public class Trajectory {
|
|||||||
var bb = entity.getBoundingBox();
|
var bb = entity.getBoundingBox();
|
||||||
var entityVelocity = entity.getVelocity();
|
var entityVelocity = entity.getVelocity();
|
||||||
// predict entity position by adding its velocity * seconds(by tick)
|
// predict entity position by adding its velocity * seconds(by tick)
|
||||||
var curEntityPos = new Vector3f(
|
|
||||||
(float) (entity.getX()),
|
|
||||||
(float) (entity.getY()),
|
|
||||||
(float) (entity.getZ())
|
|
||||||
);
|
|
||||||
var predictEntityPos = new Vector3f(
|
|
||||||
(float) (entity.getX() + entityVelocity.x * currentTick),
|
|
||||||
(float) (entity.getY() + entityVelocity.y * currentTick),
|
|
||||||
(float) (entity.getZ() + entityVelocity.z * currentTick)
|
|
||||||
);
|
|
||||||
var entityStart = new Vec3d(bb.minX, bb.minY, bb.minZ).add(entityVelocity.x * currentTick, entityVelocity.y * currentTick, entityVelocity.z * currentTick);
|
var entityStart = new Vec3d(bb.minX, bb.minY, bb.minZ).add(entityVelocity.x * currentTick, entityVelocity.y * currentTick, entityVelocity.z * currentTick);
|
||||||
var entityEnd = new Vec3d(bb.maxX, bb.maxY, bb.maxZ).add(entityVelocity.x * currentTick, entityVelocity.y * currentTick, entityVelocity.z * currentTick);
|
var entityEnd = new Vec3d(bb.maxX, bb.maxY, bb.maxZ).add(entityVelocity.x * currentTick, entityVelocity.y * currentTick, entityVelocity.z * currentTick);
|
||||||
var entityBox = new Box(entityStart, entityEnd);
|
var entityBox = new Box(entityStart, entityEnd);
|
||||||
if (this.boxIntersectsLine(start, end, entityBox)) {
|
if (this.boxIntersectsLine(start, end, entityBox)) {
|
||||||
return new TrajectoryHit(points, true); // Hit an entity
|
return new TrajectoryHit(points, entity, currentTick); // Hit an entity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +73,7 @@ public class Trajectory {
|
|||||||
if (hitResult.getType() != HitResult.Type.MISS) {
|
if (hitResult.getType() != HitResult.Type.MISS) {
|
||||||
// Check if the hit result is a block
|
// Check if the hit result is a block
|
||||||
if (hitResult.getType() == HitResult.Type.BLOCK) {
|
if (hitResult.getType() == HitResult.Type.BLOCK) {
|
||||||
return new TrajectoryHit(points, false); // Hit a block
|
return new TrajectoryHit(points, null, currentTick); // Hit a block
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +89,7 @@ public class Trajectory {
|
|||||||
break; // Prevent infinite loop
|
break; // Prevent infinite loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new TrajectoryHit(points, false); // No hit
|
return new TrajectoryHit(points, null, currentTick); // No hit
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean boxIntersectsLine(Vec3d start, Vec3d end, Box box) {
|
private boolean boxIntersectsLine(Vec3d start, Vec3d end, Box box) {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package org.walruslab.kepler.trajectory;
|
package org.walruslab.kepler.trajectory;
|
||||||
|
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
import net.minecraft.util.Pair;
|
import net.minecraft.util.Pair;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public record TrajectoryHit(List<Vector3f> points, boolean hitEntity) {
|
public record TrajectoryHit(List<Vector3f> points, Entity hitEntity, int ticks) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package org.walruslab.kepler.trajectory;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.opengl.GlStateManager;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
|
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
|
||||||
|
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.render.RenderLayer;
|
||||||
|
import net.minecraft.client.render.Tessellator;
|
||||||
|
import net.minecraft.client.render.VertexFormats;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.entity.projectile.ArrowEntity;
|
||||||
|
import net.minecraft.item.BowItem;
|
||||||
|
import net.minecraft.item.Items;
|
||||||
|
import net.minecraft.predicate.entity.EntityPredicates;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.util.TypeFilter;
|
||||||
|
import net.minecraft.util.math.Box;
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
import net.minecraft.util.math.Vec3i;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
|
import org.walruslab.kepler.trajectory.item.ShootableItemFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TrajectoryRenderer {
|
||||||
|
private final ParallelTrajectoryManager trajectoryManager = new ParallelTrajectoryManager();
|
||||||
|
|
||||||
|
public void setupRenderLine(PlayerEntity player) {
|
||||||
|
player.sendMessage(Text.of("Kepler: TrajectoryRenderer setupRenderLine"), false);
|
||||||
|
WorldRenderEvents.END.register((drawContext) -> {
|
||||||
|
var world = player.getWorld();
|
||||||
|
|
||||||
|
var entities = world.getEntitiesByClass(LivingEntity.class, new Box(player.getPos().subtract(Vec3d.of(new Vec3i(100, 100, 100))), player.getPos().add(Vec3d.of(new Vec3i(100, 100, 100)))), EntityPredicates.VALID_ENTITY);
|
||||||
|
var arrows = world.getEntitiesByType(TypeFilter.instanceOf(ArrowEntity.class), new Box(player.getPos().subtract(Vec3d.of(new Vec3i(100, 100, 100))), player.getPos().add(Vec3d.of(new Vec3i(100, 100, 100)))), EntityPredicates.VALID_ENTITY);
|
||||||
|
|
||||||
|
trajectoryManager.calculateTrajectories(arrows);
|
||||||
|
|
||||||
|
var hits = trajectoryManager.getLastHits();
|
||||||
|
if (hits != null) {
|
||||||
|
for (var hit : hits) {
|
||||||
|
if (hit != null)
|
||||||
|
renderForArrow(hit, drawContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (LivingEntity entity : entities) {
|
||||||
|
var activeItem = entity.getMainHandStack();
|
||||||
|
if (activeItem != null && (activeItem.isOf(Items.BOW) || activeItem.isOf(Items.CROSSBOW))) {
|
||||||
|
renderForPlayer(entity, drawContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderForArrow(TrajectoryHit hit, WorldRenderContext drawContext) {
|
||||||
|
var points = hit.points();
|
||||||
|
|
||||||
|
if (points.size() >= 2) {
|
||||||
|
if (hit.hitEntity() != null) {
|
||||||
|
renderLine(drawContext, points, 0.0f, 1.0f, 0.0f); // Red for hit
|
||||||
|
} else {
|
||||||
|
renderLine(drawContext, points, 1.0f, 0.0f, 1.0f); // Green for no hit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderForPlayer(LivingEntity player, WorldRenderContext drawContext) {
|
||||||
|
// get all error entity in player world
|
||||||
|
var world = player.getWorld();
|
||||||
|
|
||||||
|
var activeItem = player.getMainHandStack();
|
||||||
|
float progress = 1.0f;
|
||||||
|
if (activeItem.isOf(Items.BOW)) {
|
||||||
|
progress = BowItem.getPullProgress(player.getItemUseTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirDouble = player.getRotationVec(1.0f);
|
||||||
|
|
||||||
|
var dir = new Vector3f(
|
||||||
|
(float) dirDouble.x,
|
||||||
|
(float) dirDouble.y,
|
||||||
|
(float) dirDouble.z
|
||||||
|
).normalize();
|
||||||
|
|
||||||
|
var pos = player.getEyePos();
|
||||||
|
var playerVelocity = player.getVelocity();
|
||||||
|
|
||||||
|
var item = ShootableItemFactory.create(activeItem);
|
||||||
|
if (item != null) {
|
||||||
|
var tr = item.makeTrajectory(pos.toVector3f(), dir, playerVelocity.toVector3f(), progress);
|
||||||
|
var hit = tr.getTrajectoryPoints(world, 0.1f, player);
|
||||||
|
|
||||||
|
var points = hit.points();
|
||||||
|
|
||||||
|
if (points.size() >= 2) {
|
||||||
|
var playerRight = dir.cross(new Vector3f(0, 1, 0)).normalize();
|
||||||
|
// move points to the right, closest point -> move 1, farthest point -> move 0, linear
|
||||||
|
for (int i = 0; i < points.size(); i++) {
|
||||||
|
var point = points.get(i);
|
||||||
|
var rightOffset = 0.05;
|
||||||
|
var dx = (float) (point.x + playerRight.x * rightOffset * (1 - ((float) i / points.size())));
|
||||||
|
var dy = (float) (point.y + playerRight.y * rightOffset * (1 - ((float) i / points.size())));
|
||||||
|
var dz = (float) (point.z + playerRight.z * rightOffset * (1 - ((float) i / points.size())));
|
||||||
|
|
||||||
|
points.set(i, new Vector3f(dx, dy, dz));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the trajectory line
|
||||||
|
if (hit.hitEntity() != null) {
|
||||||
|
renderLine(drawContext, points, 0.0f, 1.0f, 0.0f); // Red for hit
|
||||||
|
} else {
|
||||||
|
renderLine(drawContext, points, 1.0f, 0.0f, 1.0f); // Green for no hit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderLine(WorldRenderContext drawContext, List<Vector3f> points, float r, float g, float b) {
|
||||||
|
// enable z-buffer
|
||||||
|
GlStateManager._enableDepthTest();
|
||||||
|
GlStateManager._depthFunc(GL11.GL_LEQUAL);
|
||||||
|
GlStateManager._enableBlend();
|
||||||
|
|
||||||
|
var tesselator = Tessellator.getInstance();
|
||||||
|
var buf = tesselator.begin(VertexFormat.DrawMode.DEBUG_LINE_STRIP, VertexFormats.POSITION_COLOR);
|
||||||
|
var mat = drawContext.positionMatrix();
|
||||||
|
|
||||||
|
var playerPos = MinecraftClient.getInstance().getCameraEntity().getCameraPosVec(0);
|
||||||
|
|
||||||
|
for (var point : points) {
|
||||||
|
float dx = (float) (point.x - playerPos.x);
|
||||||
|
float dy = (float) (point.y - playerPos.y);
|
||||||
|
float dz = (float) (point.z - playerPos.z);
|
||||||
|
|
||||||
|
buf.vertex(mat, dx, dy, dz);
|
||||||
|
buf.color(r, g, b, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderLayer.getDebugLineStrip(1).draw(buf.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.walruslab.kepler.trajectory.item;
|
||||||
|
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.walruslab.kepler.trajectory.Trajectory;
|
||||||
|
|
||||||
|
public class ShootableBow implements ShootableItem {
|
||||||
|
public final float speed = 3.0f;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Trajectory makeTrajectory(Vector3f pos, Vector3f dir, Vector3f bias, float mul) {
|
||||||
|
var velocity = dir.mul(speed * mul).add(bias);
|
||||||
|
return new Trajectory(pos, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.walruslab.kepler.trajectory.item;
|
||||||
|
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.walruslab.kepler.trajectory.Trajectory;
|
||||||
|
|
||||||
|
public class ShootableCrossbow implements ShootableItem {
|
||||||
|
public final float speed = 3.15f;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Trajectory makeTrajectory(Vector3f pos, Vector3f dir, Vector3f bias, float mul) {
|
||||||
|
var velocity = dir.mul(speed * mul).add(bias);
|
||||||
|
return new Trajectory(pos, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.walruslab.kepler.trajectory.item;
|
||||||
|
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.walruslab.kepler.trajectory.Trajectory;
|
||||||
|
|
||||||
|
public interface ShootableItem {
|
||||||
|
public Trajectory makeTrajectory(Vector3f pos, Vector3f dir, Vector3f bias, float mul);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.walruslab.kepler.trajectory.item;
|
||||||
|
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.item.Items;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class ShootableItemFactory {
|
||||||
|
public static @Nullable ShootableItem create(ItemStack itemStack) {
|
||||||
|
if (itemStack.isOf(Items.BOW))
|
||||||
|
return new ShootableBow();
|
||||||
|
else if (itemStack.isOf(Items.CROSSBOW))
|
||||||
|
return new ShootableCrossbow();
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user