Merge remote-tracking branch 'origin/master' into crashreports

This commit is contained in:
Sergey Karmanov 2020-11-15 22:41:57 +03:00
commit ccda1eff74
37 changed files with 1820 additions and 716 deletions

View File

@ -18,10 +18,12 @@ dependencies {
implementation 'ru.windcorp.fork.io.github.java-graphics:glm:1.0.1'
// log4j
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'
testImplementation 'junit:junit:4.12'
// See also LWJGL dependencies below
}
/*
@ -77,3 +79,22 @@ dependencies {
}
// LWJGL END
jar {
manifest {
attributes(
"Main-Class": "ru.windcorp.progressia.client.ProgressiaClientMain",
"Class-Path": configurations.runtimeClasspath.collect { "lib/" + it.getName() }.join(' ')
)
}
}
/*
* Copies runtime dependencies to a prespecified location so they can be packaged properly.
*/
task copyLibs(type: Copy) {
into "${libsDir}/lib"
from configurations.runtimeClasspath
}
build.dependsOn(copyLibs)

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.IntFunction;
public class StringUtil {
@ -775,4 +776,35 @@ public class StringUtil {
else return (char) ('A' - 0xA + value);
}
public static String replaceAll(String source, String substring, String replacement) {
Objects.requireNonNull(source, "source");
Objects.requireNonNull(substring, "substring");
if (substring.isEmpty()) {
throw new IllegalArgumentException("substring is empty");
}
if (!source.contains(substring)) { // also passes if source is empty
return source;
}
if (substring.equals(replacement)) { // null-safe
return source;
}
StringBuilder sb = new StringBuilder(2 * source.length());
for (int i = 0; i < source.length() - substring.length() + 1; ++i) {
if (source.startsWith(substring, i)) {
if (replacement != null) {
sb.append(replacement);
}
} else {
sb.append(source.charAt(i));
}
}
return sb.toString();
}
}

View File

@ -3,10 +3,10 @@ package ru.windcorp.progressia.client;
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.flat.LayerTestUI;
import ru.windcorp.progressia.client.graphics.gui.LayerTestGUI;
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerTestGUI;
public class ClientState {

View File

@ -1,125 +0,0 @@
/*******************************************************************************
* Progressia
* Copyright (C) 2020 Wind Corporation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*******************************************************************************/
package ru.windcorp.progressia.client.graphics.gui;
import com.google.common.eventbus.Subscribe;
import glm.vec._2.i.Vec2i;
import org.lwjgl.glfw.GLFW;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.client.localization.MutableString;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
public class LayerTestGUI extends GUILayer {
private static class DebugComponent extends Component {
private final int color;
public DebugComponent(String name, Vec2i size, int color) {
super(name);
this.color = color;
setPreferredSize(size);
addListener(new Object() {
@Subscribe
public void onHoverChanged(HoverEvent e) {
requestReassembly();
}
});
addListener(KeyEvent.class, this::onClicked);
}
private boolean onClicked(KeyEvent event) {
if (!event.isMouse()) {
return false;
} else if (event.isPress() && event.isLeftMouseButton()) {
System.out.println("You pressed a Component!");
}
return true;
}
@Override
protected void assembleSelf(RenderTarget target) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.BLACK);
target.fill(
getX() + 2, getY() + 2,
getWidth() - 4, getHeight() - 4,
isHovered() ? Colors.DEBUG_YELLOW : color
);
}
}
public LayerTestGUI() {
super("LayerTestGui", new LayoutAlign(1, 0.75, 5));
Panel panel = new Panel("Alex", new LayoutVertical(5));
panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), 0x44FF44));
Component charlie = new DebugComponent("Charlie", null, 0x222222);
charlie.setLayout(new LayoutVertical(5));
//Debug
Localizer.getInstance().setLanguage("ru-RU");
MutableString epsilon = new MutableStringLocalized("Epsilon")
.addListener(() -> ((Label)charlie.getChild(0)).update()).format(34, "thirty-four");
// These two are swapped in code due to a bug in layouts, fixing ATM
charlie.addChild(
new Label(
"Delta",
new Font().withColor(0xCCBB44).deriveShadow().deriveBold(),
"Пре-альфа!"
)
);
charlie.addChild(
new Label(
"Epsilon",
new Font().withColor(0x4444BB).deriveItalic(),
() -> epsilon.get().concat("\u269b")
)
);
panel.addChild(charlie);
charlie.addListener(KeyEvent.class, e -> {
if(e.isPress() && e.getKey() == GLFW.GLFW_KEY_L) {
Localizer localizer = Localizer.getInstance();
if (localizer.getLanguage().equals("ru-RU")) {
localizer.setLanguage("en-US");
} else {
localizer.setLanguage("ru-RU");
}
return true;
} return false;
});
charlie.setFocusable(true);
charlie.takeFocus();
getRoot().addChild(panel);
}
}

View File

@ -121,5 +121,14 @@ public class LambdaModel extends DynamicModel {
}
}
public static LambdaModel animate(Renderable model, TransformGetter transform) {
return new LambdaModel(
new Renderable[] { model },
new Mat4[] { new Mat4() },
new boolean[] { true },
new TransformGetter[] { transform }
);
}
}

View File

@ -19,10 +19,10 @@ public class ComplexTexture {
this.primitive = primitive;
this.assumedWidth = abstractWidth
* primitive.getWidth() / (float) primitive.getBufferWidth();
/ (float) primitive.getWidth() * primitive.getBufferWidth();
this.assumedHeight = abstractHeight
* primitive.getHeight() / (float) primitive.getBufferHeight();
/ (float) primitive.getHeight() * primitive.getBufferHeight();
}
public Texture get(int x, int y, int width, int height) {

View File

@ -271,6 +271,10 @@ public class Camera {
currentModeIndex++;
}
}
public int getCurrentModeIndex() {
return currentModeIndex;
}
public float getLastAnchorYaw() {
return lastAnchorYaw;

View File

@ -20,45 +20,27 @@ package ru.windcorp.progressia.client.graphics.world;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.glfw.GLFW;
import glm.Glm;
import glm.mat._3.Mat3;
import glm.vec._2.Vec2;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.comms.controls.InputBasedControls;
import ru.windcorp.progressia.client.graphics.Layer;
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.CollisionClock;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.collision.CompoundCollisionModel;
import ru.windcorp.progressia.common.collision.colliders.Collider;
import ru.windcorp.progressia.common.util.FloatMathUtils;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.test.AABBRenderer;
import ru.windcorp.progressia.test.CollisionModelRenderer;
import ru.windcorp.progressia.test.TestPlayerControls;
public class LayerWorld extends Layer {
private final Mat3 angMat = new Mat3();
private int movementForward = 0;
private int movementRight = 0;
private int movementUp = 0;
private final WorldRenderHelper helper = new WorldRenderHelper();
private final Client client;
private final InputBasedControls inputBasedControls;
private final TestPlayerControls tmp_testControls = TestPlayerControls.getInstance();
public LayerWorld(Client client) {
super("World");
@ -79,40 +61,12 @@ public class LayerWorld extends Layer {
@Override
protected void doRender() {
if (client.getLocalPlayer() != null) {
tmp_handleControls();
}
Camera camera = client.getCamera();
if (camera.hasAnchor()) {
renderWorld();
}
}
private void tmp_handleControls() {
EntityData player = client.getLocalPlayer();
angMat.identity().rotateZ(player.getYaw());
Vec3 movement = Vectors.grab3();
// Horizontal and vertical max control speed
final float movementSpeed = 0.1f * 60.0f;
// (0; 1], 1 is instant change, 0 is no control authority
final float controlAuthority = 0.1f;
movement.set(movementForward, -movementRight, 0);
if (movementForward != 0 && movementRight != 0) movement.normalize();
angMat.mul_(movement); // bug in jglm, .mul() and mul_() are swapped
movement.z = movementUp;
movement.mul(movementSpeed);
movement.sub(player.getVelocity());
movement.mul(controlAuthority);
player.getVelocity().add(movement);
Vectors.release(movement);
}
private void renderWorld() {
client.getCamera().apply(helper);
FaceCulling.push(true);
@ -128,150 +82,75 @@ public class LayerWorld extends Layer {
private final Collider.ColliderWorkspace tmp_colliderWorkspace = new Collider.ColliderWorkspace();
private final List<Collideable> tmp_collideableList = new ArrayList<>();
private static final boolean RENDER_AABBS = true;
private static final boolean RENDER_COLLISION_MODELS = false;
private void tmp_doEveryFrame() {
float tickLength = (float) GraphicsInterface.getFrameLength();
try {
if (RENDER_AABBS) {
for (EntityData data : this.client.getWorld().getData().getEntities()) {
CollisionModel model = data.getCollisionModel();
if (model instanceof AABB) {
AABBRenderer.renderAABB((AABB) model, helper);
} else if (model instanceof CompoundCollisionModel) {
AABBRenderer.renderAABBsInCompound((CompoundCollisionModel) model, helper);
}
}
}
tmp_performCollisions(tickLength);
tmp_collideableList.clear();
tmp_collideableList.addAll(this.client.getWorld().getData().getEntities());
Collider.performCollisions(
tmp_collideableList,
new CollisionClock() {
private float t = 0;
@Override
public float getTime() {
return t;
}
@Override
public void advanceTime(float change) {
t += change;
}
},
(float) GraphicsInterface.getFrameLength(),
tmp_colliderWorkspace
);
final float frictionCoeff = 1 - 1e-2f;
tmp_testControls.applyPlayerControls();
for (EntityData data : this.client.getWorld().getData().getEntities()) {
data.getVelocity().mul(frictionCoeff);
tmp_applyFriction(data);
tmp_applyGravity(data, tickLength);
tmp_renderCollisionModel(data);
}
} catch (Exception e) {
} catch (Throwable e) {
e.printStackTrace();
System.err.println("OLEGSHA is to blame. Tell him he vry stupiDD!!");
System.exit(31337);
}
}
private void tmp_renderCollisionModel(EntityData entity) {
if (RENDER_COLLISION_MODELS) {
CollisionModelRenderer.renderCollisionModel(entity.getCollisionModel(), helper);
}
}
private void tmp_performCollisions(float tickLength) {
tmp_collideableList.clear();
tmp_collideableList.addAll(this.client.getWorld().getData().getEntities());
Collider.performCollisions(
tmp_collideableList,
this.client.getWorld().getData(),
tickLength,
tmp_colliderWorkspace
);
}
private void tmp_applyFriction(EntityData entity) {
final float frictionCoeff = 1 - 1e-5f;
entity.getVelocity().mul(frictionCoeff);
}
private void tmp_applyGravity(EntityData entity, float tickLength) {
if (ClientState.getInstance().getLocalPlayer() == entity && tmp_testControls.isFlying()) {
return;
}
final float gravitationalAcceleration;
if (tmp_testControls.useMinecraftGravity()) {
gravitationalAcceleration = 32f * Units.METERS_PER_SECOND_SQUARED; // plz dont sue me M$
} else {
gravitationalAcceleration = 9.81f * Units.METERS_PER_SECOND_SQUARED;
}
entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
}
@Override
protected void handleInput(Input input) {
if (input.isConsumed()) return;
InputEvent event = input.getEvent();
if (event instanceof KeyEvent) {
if (onKeyEvent((KeyEvent) event)) {
input.consume();
}
} else if (event instanceof CursorMoveEvent) {
onMouseMoved((CursorMoveEvent) event);
input.consume();
}
tmp_testControls.handleInput(input);
if (!input.isConsumed()) {
inputBasedControls.handleInput(input);
}
}
private boolean flag = true;
private boolean onKeyEvent(KeyEvent event) {
if (event.isRepeat()) return false;
int multiplier = event.isPress() ? 1 : -1;
switch (event.getKey()) {
case GLFW.GLFW_KEY_W:
movementForward += +1 * multiplier;
break;
case GLFW.GLFW_KEY_S:
movementForward += -1 * multiplier;
break;
case GLFW.GLFW_KEY_A:
movementRight += -1 * multiplier;
break;
case GLFW.GLFW_KEY_D:
movementRight += +1 * multiplier;
break;
case GLFW.GLFW_KEY_SPACE:
movementUp += +1 * multiplier;
break;
case GLFW.GLFW_KEY_LEFT_SHIFT:
movementUp += -1 * multiplier;
break;
case GLFW.GLFW_KEY_ESCAPE:
if (!event.isPress()) return false;
if (flag) {
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
} else {
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
}
flag = !flag;
break;
case GLFW.GLFW_KEY_F5:
if (!event.isPress()) return false;
if (client.getCamera().hasAnchor()) {
client.getCamera().selectNextMode();
}
break;
default:
return false;
}
return true;
}
private void onMouseMoved(CursorMoveEvent event) {
if (!flag) return;
final float yawScale = -0.002f;
final float pitchScale = yawScale;
EntityData player = client.getLocalPlayer();
if (player != null) {
normalizeAngles(player.getDirection().add(
(float) (event.getChangeX() * yawScale),
(float) (event.getChangeY() * pitchScale)
));
}
}
private void normalizeAngles(Vec2 dir) {
// Normalize yaw
dir.x = FloatMathUtils.normalizeAngle(dir.x);
// Clamp pitch
dir.y = Glm.clamp(
dir.y, -FloatMathUtils.PI_F/2, +FloatMathUtils.PI_F/2
);
}
}

View File

@ -0,0 +1,116 @@
package ru.windcorp.progressia.client.world.entity;
import static java.lang.Math.*;
import static ru.windcorp.progressia.common.util.FloatMathUtils.*;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class HumanoidModel extends NPedModel {
protected static abstract class Limb extends BodyPart {
private final float animationOffset;
public Limb(
Renderable renderable, Vec3 joint,
float animationOffset
) {
super(renderable, joint);
this.animationOffset = animationOffset;
}
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
float value = sin(phase);
float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter();
mat.rotateY(value * amplitude);
}
protected abstract float getSwingAmplitude(HumanoidModel model);
}
public static class Leg extends Limb {
public Leg(
Renderable renderable, Vec3 joint,
float animationOffset
) {
super(renderable, joint, animationOffset);
}
@Override
protected float getSwingAmplitude(HumanoidModel model) {
return model.walkingLegSwing;
}
}
public static class Arm extends Limb {
public Arm(
Renderable renderable, Vec3 joint,
float animationOffset
) {
super(renderable, joint, animationOffset);
}
@Override
protected float getSwingAmplitude(HumanoidModel model) {
return model.walkingArmSwing;
}
}
private final Arm leftArm;
private final Arm rightArm;
private final Leg leftLeg;
private final Leg rightLeg;
private float walkingLegSwing;
private float walkingArmSwing;
public HumanoidModel(
EntityData entity,
Body body, Head head,
Arm leftArm, Arm rightArm,
Leg leftLeg, Leg rightLeg,
float scale
) {
super(entity, body, head, scale);
this.leftArm = leftArm;
this.rightArm = rightArm;
this.leftLeg = leftLeg;
this.rightLeg = rightLeg;
}
@Override
protected void renderBodyParts(ShapeRenderHelper renderer) {
super.renderBodyParts(renderer);
leftArm.render(renderer, this);
rightArm.render(renderer, this);
leftLeg.render(renderer, this);
rightLeg.render(renderer, this);
}
public float getWalkingArmSwing() {
return walkingArmSwing;
}
public float getWalkingLegSwing() {
return walkingLegSwing;
}
public HumanoidModel setWalkingLegSwing(float walkingLegSwing) {
this.walkingLegSwing = walkingLegSwing;
return this;
}
public HumanoidModel setWalkingArmSwing(float walkingArmSwing) {
this.walkingArmSwing = walkingArmSwing;
return this;
}
}

View File

@ -0,0 +1,284 @@
package ru.windcorp.progressia.client.world.entity;
import static java.lang.Math.atan2;
import static java.lang.Math.min;
import static java.lang.Math.pow;
import static java.lang.Math.toRadians;
import static ru.windcorp.progressia.common.util.FloatMathUtils.normalizeAngle;
import glm.Glm;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
public abstract class NPedModel extends EntityRenderable {
protected static abstract class BodyPart {
private final Renderable renderable;
private final Vec3 translation = new Vec3();
public BodyPart(Renderable renderable, Vec3 joint) {
this.renderable = renderable;
if (joint != null) {
this.translation.set(joint);
}
}
protected void render(
ShapeRenderHelper renderer, NPedModel model
) {
renderer.pushTransform().translate(translation);
applyTransform(renderer.pushTransform(), model);
renderable.render(renderer);
renderer.popTransform();
renderer.popTransform();
}
protected abstract void applyTransform(Mat4 mat, NPedModel model);
public Vec3 getTranslation() {
return translation;
}
}
public static class Body extends BodyPart {
public Body(Renderable renderable) {
super(renderable, null);
}
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
// Do nothing
}
}
public static class Head extends BodyPart {
private final float maxYaw;
private final float maxPitch;
private final Vec3 viewPoint;
public Head(
Renderable renderable, Vec3 joint,
double maxYawDegrees, double maxPitchDegrees,
Vec3 viewPoint
) {
super(renderable, joint);
this.maxYaw = (float) toRadians(maxYawDegrees);
this.maxPitch = (float) toRadians(maxPitchDegrees);
this.viewPoint = viewPoint;
}
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
mat.rotateZ(model.getHeadYaw()).rotateY(model.getHeadPitch());
}
public Vec3 getViewPoint() {
return viewPoint;
}
}
protected final Body body;
protected final Head head;
private float walkingParameter = 0;
private float velocityParameter = 0;
private float velocity = 0;
/**
* If {@link #velocity} is greater than this value, {@link #velocityParameter} is 1.0.
*/
private float maxEffectiveVelocity = 5 * Units.METERS_PER_SECOND;
/**
* If {@link #velocity} is less than {@link #maxEffectiveVelocity}, then
* {@code velocityCoeff = exp(velocity / maxEffectiveVelocity, velocityCoeffPower)}.
*/
private float velocityCoeffPower = 1;
private final float scale;
private float walkingFrequency;
private float bodyYaw = Float.NaN;
private float headYaw;
private float headPitch;
public NPedModel(EntityData data, Body body, Head head, float scale) {
super(data);
this.body = body;
this.head = head;
this.scale = scale;
}
@Override
public void render(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).rotateZ(bodyYaw);
renderBodyParts(renderer);
renderer.popTransform();
accountForVelocity();
evaluateAngles();
}
protected void renderBodyParts(ShapeRenderHelper renderer) {
body.render(renderer, this);
head.render(renderer, this);
}
private void evaluateAngles() {
float globalYaw = normalizeAngle(getData().getYaw());
if (Float.isNaN(bodyYaw)) {
bodyYaw = globalYaw;
headYaw = 0;
} else {
headYaw = normalizeAngle(globalYaw - bodyYaw);
if (headYaw > +head.maxYaw) {
bodyYaw += headYaw - +head.maxYaw;
headYaw = +head.maxYaw;
} else if (headYaw < -head.maxYaw) {
bodyYaw += headYaw - -head.maxYaw;
headYaw = -head.maxYaw;
}
}
bodyYaw = normalizeAngle(bodyYaw);
headPitch = Glm.clamp(
getData().getPitch(),
-head.maxPitch, head.maxPitch
);
}
private void accountForVelocity() {
Vec3 horizontal = Vectors.grab3();
horizontal.set(getData().getVelocity());
horizontal.z = 0;
velocity = horizontal.length();
evaluateVelocityCoeff();
// TODO switch to world time
walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
bodyYaw += velocityParameter * normalizeAngle(
(float) (atan2(horizontal.y, horizontal.x) - bodyYaw)
) * min(1, GraphicsInterface.getFrameLength() * 10);
Vectors.release(horizontal);
}
private void evaluateVelocityCoeff() {
if (velocity > maxEffectiveVelocity) {
velocityParameter = 1;
} else {
velocityParameter = (float) pow(velocity / maxEffectiveVelocity, velocityCoeffPower);
}
}
@Override
public void getViewPoint(Vec3 output) {
Mat4 m = Matrices.grab4();
Vec4 v = Vectors.grab4();
m.identity()
.scale(scale)
.rotateZ(bodyYaw)
.translate(head.getTranslation())
.rotateZ(headYaw)
.rotateY(headPitch);
v.set(head.getViewPoint(), 1);
m.mul(v);
output.set(v.x, v.y, v.z);
Vectors.release(v);
Matrices.release(m);
}
public Body getBody() {
return body;
}
public Head getHead() {
return head;
}
public float getBodyYaw() {
return bodyYaw;
}
public float getHeadYaw() {
return headYaw;
}
public float getHeadPitch() {
return headPitch;
}
/**
* Returns a number in the range [0; 1] that can be used to scale animation effects that depend on speed.
* This parameter is 0 when the entity is not moving and 1 when it's moving "fast".
* @return velocity parameter
*/
protected float getVelocityParameter() {
return velocityParameter;
}
/**
* Returns a number that can be used to parameterize animation effects that depend on walking.
* This parameter increases when the entity moves (e.g. this can be total traveled distance).
* @return walking parameter
*/
protected float getWalkingParameter() {
return walkingParameter;
}
protected float getVelocity() {
return velocity;
}
public float getScale() {
return scale;
}
protected float getWalkingFrequency() {
return walkingFrequency;
}
public NPedModel setWalkingFrequency(float walkingFrequency) {
this.walkingFrequency = walkingFrequency;
return this;
}
public float getMaxEffectiveVelocity() {
return maxEffectiveVelocity;
}
public float getVelocityCoeffPower() {
return velocityCoeffPower;
}
public NPedModel setMaxEffectiveVelocity(float maxEffectiveVelocity) {
this.maxEffectiveVelocity = maxEffectiveVelocity;
return this;
}
public NPedModel setVelocityCoeffPower(float velocityCoeffPower) {
this.velocityCoeffPower = velocityCoeffPower;
return this;
}
}

View File

@ -2,86 +2,15 @@ package ru.windcorp.progressia.client.world.entity;
import static java.lang.Math.*;
import static ru.windcorp.progressia.common.util.FloatMathUtils.*;
import static ru.windcorp.progressia.common.util.FloatMathUtils.sin;
import glm.Glm;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class QuadripedModel extends EntityRenderable {
private static abstract class BodyPart {
private final Renderable renderable;
private final Vec3 translation = new Vec3();
public BodyPart(Renderable renderable, Vec3 joint) {
this.renderable = renderable;
if (joint != null) {
this.translation.set(joint);
}
}
protected void render(
ShapeRenderHelper renderer, QuadripedModel model
) {
renderer.pushTransform().translate(translation);
applyTransform(renderer.pushTransform(), model);
renderable.render(renderer);
renderer.popTransform();
renderer.popTransform();
}
protected abstract void applyTransform(Mat4 mat, QuadripedModel model);
public Vec3 getTranslation() {
return translation;
}
}
public static class Body extends BodyPart {
public Body(Renderable renderable) {
super(renderable, null);
}
@Override
protected void applyTransform(Mat4 mat, QuadripedModel model) {
// Do nothing
}
}
public static class Head extends BodyPart {
private final float maxYaw;
private final float maxPitch;
private final Vec3 viewPoint;
public Head(
Renderable renderable, Vec3 joint,
double maxYawDegrees, double maxPitchDegrees,
Vec3 viewPoint
) {
super(renderable, joint);
this.maxYaw = (float) toRadians(maxYawDegrees);
this.maxPitch = (float) toRadians(maxPitchDegrees);
this.viewPoint = viewPoint;
}
@Override
protected void applyTransform(Mat4 mat, QuadripedModel model) {
mat.rotateZ(model.headYaw).rotateY(model.headPitch);
}
public Vec3 getViewPoint() {
return viewPoint;
}
}
public class QuadripedModel extends NPedModel {
public static class Leg extends BodyPart {
private final float animationOffset;
@ -95,33 +24,19 @@ public class QuadripedModel extends EntityRenderable {
}
@Override
protected void applyTransform(Mat4 mat, QuadripedModel model) {
mat.rotateY(sin(model.walkingFrequency * model.walkingAnimationParameter + animationOffset) * model.walkingSwing * model.velocityCoeff);
protected void applyTransform(Mat4 mat, NPedModel model) {
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
float value = sin(phase);
float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter();
mat.rotateY(value * amplitude);
}
}
private final Body body;
private final Head head;
private final Leg leftForeLeg, rightForeLeg;
private final Leg leftHindLeg, rightHindLeg;
private final float scale;
private float walkingAnimationParameter = 0;
private float velocityCoeff = 0;
private float velocity = 0;
/**
* Controls how quickly velocityCoeff approaches 1
*/
private float velocityCutoff = 10;
private float walkingFrequency = 0.15f / 60.0f;
private float walkingSwing = (float) toRadians(30);
private float bodyYaw = Float.NaN;
private float headYaw;
private float headPitch;
public QuadripedModel(
EntityData entity,
@ -132,105 +47,30 @@ public class QuadripedModel extends EntityRenderable {
float scale
) {
super(entity);
super(entity, body, head, scale);
this.body = body;
this.head = head;
this.leftForeLeg = leftForeLeg;
this.rightForeLeg = rightForeLeg;
this.leftHindLeg = leftHindLeg;
this.rightHindLeg = rightHindLeg;
this.scale = scale;
}
@Override
public void render(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).rotateZ(bodyYaw);
body.render(renderer, this);
head.render(renderer, this);
leftForeLeg.render(renderer, this);
rightForeLeg.render(renderer, this);
leftHindLeg.render(renderer, this);
rightHindLeg.render(renderer, this);
renderer.popTransform();
accountForVelocity();
evaluateAngles();
}
private void evaluateAngles() {
float globalYaw = normalizeAngle(getData().getYaw());
if (Float.isNaN(bodyYaw)) {
bodyYaw = globalYaw;
headYaw = 0;
} else {
headYaw = normalizeAngle(globalYaw - bodyYaw);
if (headYaw > +head.maxYaw) {
bodyYaw += headYaw - +head.maxYaw;
headYaw = +head.maxYaw;
} else if (headYaw < -head.maxYaw) {
bodyYaw += headYaw - -head.maxYaw;
headYaw = -head.maxYaw;
}
}
bodyYaw = normalizeAngle(bodyYaw);
headPitch = Glm.clamp(
getData().getPitch(),
-head.maxPitch, head.maxPitch
);
}
private void accountForVelocity() {
Vec3 horizontal = Vectors.grab3();
horizontal.set(getData().getVelocity());
horizontal.z = 0;
velocity = horizontal.length();
evaluateVelocityCoeff();
// TODO switch to world time
walkingAnimationParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
bodyYaw += velocityCoeff * normalizeAngle(
(float) (atan2(horizontal.y, horizontal.x) - bodyYaw)
) * min(1, GraphicsInterface.getFrameLength() * 10);
Vectors.release(horizontal);
protected void renderBodyParts(ShapeRenderHelper renderer) {
super.renderBodyParts(renderer);
this.leftForeLeg.render(renderer, this);
this.rightForeLeg.render(renderer, this);
this.leftHindLeg.render(renderer, this);
this.rightHindLeg.render(renderer, this);
}
private void evaluateVelocityCoeff() {
if (velocity * velocityCutoff > 1) {
velocityCoeff = 1;
} else {
velocityCoeff = velocity * velocityCutoff;
velocityCoeff *= velocityCoeff;
}
public float getWalkingSwing() {
return walkingSwing;
}
@Override
public void getViewPoint(Vec3 output) {
Mat4 m = Matrices.grab4();
Vec4 v = Vectors.grab4();
m.identity()
.scale(scale)
.rotateZ(bodyYaw)
.translate(head.getTranslation())
.rotateZ(headYaw)
.rotateY(headPitch);
v.set(head.getViewPoint(), 1);
m.mul(v);
output.set(v.x, v.y, v.z);
Vectors.release(v);
Matrices.release(m);
public QuadripedModel setWalkingSwing(float walkingSwing) {
this.walkingSwing = walkingSwing;
return this;
}
}

View File

@ -0,0 +1,56 @@
package ru.windcorp.progressia.common;
public class Units {
// Base units
// We're SI.
public static final float METERS = 1;
public static final float KILOGRAMS = 1;
public static final float SECONDS = 1;
// Length
public static final float CENTIMETERS = METERS / 100;
public static final float MILLIMETERS = METERS / 1000;
public static final float KILOMETERS = METERS * 1000;
// Surface
public static final float SQUARE_CENTIMETERS = CENTIMETERS * CENTIMETERS;
public static final float SQUARE_METERS = METERS * METERS;
public static final float SQUARE_MILLIMETERS = MILLIMETERS * MILLIMETERS;
public static final float SQUARE_KILOMETERS = KILOMETERS * KILOMETERS;
// Volume
public static final float CUBIC_CENTIMETERS = CENTIMETERS * CENTIMETERS * CENTIMETERS;
public static final float CUBIC_METERS = METERS * METERS * METERS;
public static final float CUBIC_MILLIMETERS = MILLIMETERS * MILLIMETERS * MILLIMETERS;
public static final float CUBIC_KILOMETERS = KILOMETERS * KILOMETERS * KILOMETERS;
// Mass
public static final float GRAMS = KILOGRAMS / 1000;
public static final float TONNES = KILOGRAMS * 1000;
// Density
public static final float KILOGRAMS_PER_CUBIC_METER = KILOGRAMS / CUBIC_METERS;
public static final float GRAMS_PER_CUBIC_CENTIMETER = GRAMS / CUBIC_CENTIMETERS;
// Time
public static final float MILLISECONDS = SECONDS / 1000;
public static final float MINUTES = SECONDS * 60;
public static final float HOURS = MINUTES * 60;
public static final float DAYS = HOURS * 24;
// Frequency
public static final float HERTZ = 1 / SECONDS;
public static final float KILOHERTZ = HERTZ * 1000;
// Velocity
public static final float METERS_PER_SECOND = METERS / SECONDS;
public static final float KILOMETERS_PER_HOUR = KILOMETERS / HOURS;
// Acceleration
public static final float METERS_PER_SECOND_SQUARED = METERS_PER_SECOND / SECONDS;
// Force
public static final float NEWTONS = METERS_PER_SECOND_SQUARED * KILOGRAMS;
}

View File

@ -1,93 +1,113 @@
package ru.windcorp.progressia.common.collision;
import java.util.Collection;
import java.util.Map;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class AABB implements CollisionModel {
/**
* An implementation of an
* <a href="https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box">Axis-Aligned Bounding Box</a>.
* @author javapony
*/
public class AABB implements AABBoid {
private class AABBWallImpl implements Wall {
private final Vec3 originOffset = new Vec3();
private final Vec3 widthSelector = new Vec3();
private final Vec3 heightSelector = new Vec3();
private final Map<BlockFace, CollisionWall> faces = BlockFace.mapToFaces(
new CollisionWall(-0.5f, -0.5f, -0.5f, +1, 0, 0, 0, 0, +1),
new CollisionWall(+0.5f, -0.5f, -0.5f, 0, +1, 0, 0, 0, +1),
new CollisionWall(+0.5f, +0.5f, -0.5f, -1, 0, 0, 0, 0, +1),
new CollisionWall(-0.5f, +0.5f, -0.5f, 0, -1, 0, 0, 0, +1),
new CollisionWall(-0.5f, -0.5f, +0.5f, +1, 0, 0, 0, +1, 0),
new CollisionWall(-0.5f, -0.5f, -0.5f, 0, +1, 0, +1, 0, 0)
);
public AABBWallImpl(
float ox, float oy, float oz,
float wx, float wy, float wz,
float hx, float hy, float hz
) {
this.originOffset.set(ox, oy, oz);
this.widthSelector.set(wx, wy, wz);
this.heightSelector.set(hx, hy, hz);
}
@Override
public void getOrigin(Vec3 output) {
output.set(originOffset).mul(AABB.this.getSize()).add(AABB.this.getOrigin());
}
@Override
public void getWidth(Vec3 output) {
output.set(AABB.this.getSize()).mul(widthSelector);
}
@Override
public void getHeight(Vec3 output) {
output.set(AABB.this.getSize()).mul(heightSelector);
}
}
public static final AABB UNIT_CUBE = new AABB(0, 0, 0, 1, 1, 1);
private final Wall[] walls = new Wall[] {
new AABBWallImpl(-0.5f, -0.5f, +0.5f, +1, 0, 0, 0, +1, 0), // Top
new AABBWallImpl(-0.5f, -0.5f, -0.5f, 0, +1, 0, +1, 0, 0), // Bottom
new AABBWallImpl(+0.5f, -0.5f, -0.5f, 0, +1, 0, 0, 0, +1), // North
new AABBWallImpl(-0.5f, +0.5f, -0.5f, 0, -1, 0, 0, 0, +1), // South
new AABBWallImpl(+0.5f, +0.5f, -0.5f, -1, 0, 0, 0, 0, +1), // West
new AABBWallImpl(-0.5f, -0.5f, -0.5f, +1, 0, 0, 0, 0, +1) // East
};
private final Vec3 origin = new Vec3();
private final Vec3 size = new Vec3();
public AABB(Vec3 origin, Vec3 size) {
this.origin.set(origin);
this.size.set(size);
for (CollisionWall wall : getFaces()) {
wall.moveOrigin(origin);
wall.getWidth().mul(size);
wall.getHeight().mul(size);
}
this(origin.x, origin.y, origin.z, size.x, size.y, size.z);
}
public AABB(
float ox, float oy, float oz,
float ox, float oy, float oz,
float xSize, float ySize, float zSize
) {
this.origin.set(ox, oy, oz);
this.size.set(xSize, ySize, zSize);
for (CollisionWall wall : getFaces()) {
wall.moveOrigin(ox, oy, oz);
wall.getWidth().mul(xSize, ySize, zSize);
wall.getHeight().mul(xSize, ySize, zSize);
}
}
public Collection<CollisionWall> getFaces() {
return faces.values();
}
public Vec3 getOrigin() {
return origin;
}
@Override
public void getOrigin(Vec3 output) {
output.set(origin);
}
@Override
public void setOrigin(Vec3 origin) {
for (CollisionWall wall : getFaces()) {
wall.getOrigin().sub(this.origin).add(origin);
}
this.origin.set(origin);
}
@Override
public void moveOrigin(Vec3 displacement) {
for (CollisionWall wall : getFaces()) {
wall.getOrigin().add(displacement);
}
this.origin.add(displacement);
}
public Vec3 getSize() {
return size;
}
@Override
public void getSize(Vec3 output) {
output.set(size);
}
public void setSize(Vec3 size) {
setSize(size.x, size.y, size.z);
}
public void setSize(float xSize, float ySize, float zSize) {
for (CollisionWall wall : getFaces()) {
wall.getWidth().div(this.size).mul(xSize, ySize, zSize);
wall.getHeight().div(this.size).mul(xSize, ySize, zSize);
wall.getOrigin().sub(getOrigin()).div(this.size).mul(xSize, ySize, zSize).add(getOrigin());
}
this.size.set(xSize, ySize, zSize);
}
@Override
public Wall getWall(int faceId) {
// No, we don't support Apple.
return walls[faceId];
}
}

View File

@ -0,0 +1,17 @@
package ru.windcorp.progressia.common.collision;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.block.BlockFace;
public interface AABBoid extends CollisionModel {
void getOrigin(Vec3 output);
void getSize(Vec3 output);
default Wall getWall(BlockFace face) {
return getWall(face.getId());
}
Wall getWall(int faceId);
}

View File

@ -1,8 +0,0 @@
package ru.windcorp.progressia.common.collision;
public interface CollisionClock {
float getTime();
void advanceTime(float change);
}

View File

@ -0,0 +1,79 @@
package ru.windcorp.progressia.common.collision;
import java.util.function.Consumer;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.Vectors;
import static java.lang.Math.*;
public class CollisionPathComputer {
public static void forEveryBlockInCollisionPath(
Collideable coll,
float maxTime,
Consumer<Vec3i> action
) {
Vec3 displacement = Vectors.grab3();
coll.getCollideableVelocity(displacement);
displacement.mul(maxTime);
handleModel(coll.getCollisionModel(), displacement, action);
Vectors.release(displacement);
}
private static void handleModel(
CollisionModel model,
Vec3 displacement,
Consumer<Vec3i> action
) {
if (model instanceof CompoundCollisionModel) {
for (CollisionModel subModel : ((CompoundCollisionModel) model).getModels()) {
handleModel(subModel, displacement, action);
}
} else if (model instanceof AABBoid) {
handleAABBoid((AABBoid) model, displacement, action);
} else {
throw new RuntimeException("not supported");
}
}
private static void handleAABBoid(AABBoid model, Vec3 displacement, Consumer<Vec3i> action) {
Vec3 size = Vectors.grab3();
Vec3 origin = Vectors.grab3();
model.getOrigin(origin);
model.getSize(size);
origin.mul(2).sub(size).div(2); // Subtract 0.5*size
Vec3i pos = Vectors.grab3i();
for (
pos.x = (int) floor(origin.x + min(0, size.x) + min(0, displacement.x));
pos.x <= (int) ceil(origin.x + max(0, size.x) + max(0, displacement.x));
pos.x += 1
) {
for (
pos.y = (int) floor(origin.y + min(0, size.y) + min(0, displacement.y));
pos.y <= (int) ceil(origin.y + max(0, size.y) + max(0, displacement.y));
pos.y += 1
) {
for (
pos.z = (int) floor(origin.z + min(0, size.z) + min(0, displacement.z));
pos.z <= (int) ceil(origin.z + max(0, size.z) + max(0, displacement.z));
pos.z += 1
) {
action.accept(pos);
}
}
}
Vectors.release(origin);
Vectors.release(size);
Vectors.release(pos);
}
}

View File

@ -1,55 +0,0 @@
package ru.windcorp.progressia.common.collision;
import glm.vec._3.Vec3;
public class CollisionWall {
private final Vec3 origin = new Vec3();
private final Vec3 width = new Vec3();
private final Vec3 height = new Vec3();
public CollisionWall(Vec3 origin, Vec3 width, Vec3 height) {
this.origin.set(origin);
this.width.set(width);
this.height.set(height);
}
public CollisionWall(
float ox, float oy, float oz,
float wx, float wy, float wz,
float hx, float hy, float hz
) {
this.origin.set(ox, oy, oz);
this.width.set(wx, wy, wz);
this.height.set(hx, hy, hz);
}
public Vec3 getOrigin() {
return origin;
}
public Vec3 getWidth() {
return width;
}
public Vec3 getHeight() {
return height;
}
public void setOrigin(Vec3 origin) {
setOrigin(origin.x, origin.y, origin.z);
}
public void setOrigin(float x, float y, float z) {
this.origin.set(x, y, z);
}
public void moveOrigin(Vec3 displacement) {
moveOrigin(displacement.x, displacement.y, displacement.z);
}
public void moveOrigin(float dx, float dy, float dz) {
this.origin.add(dx, dy, dz);
}
}

View File

@ -8,9 +8,9 @@ import glm.vec._3.Vec3;
public class CompoundCollisionModel implements CollisionModel {
private final Collection<CollisionModel> models;
private final Collection<? extends CollisionModel> models;
public CompoundCollisionModel(Collection<CollisionModel> models) {
public CompoundCollisionModel(Collection<? extends CollisionModel> models) {
this.models = models;
}
@ -18,7 +18,7 @@ public class CompoundCollisionModel implements CollisionModel {
this(ImmutableList.copyOf(models));
}
public Collection<CollisionModel> getModels() {
public Collection<? extends CollisionModel> getModels() {
return models;
}

View File

@ -0,0 +1,105 @@
package ru.windcorp.progressia.common.collision;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class TranslatedAABB implements AABBoid {
private class TranslatedAABBWall implements Wall {
private final int id;
public TranslatedAABBWall(int id) {
this.id = id;
}
@Override
public void getOrigin(Vec3 output) {
parent.getWall(id).getOrigin(output);
output.add(translation);
}
@Override
public void getWidth(Vec3 output) {
parent.getWall(id).getWidth(output);
}
@Override
public void getHeight(Vec3 output) {
parent.getWall(id).getHeight(output);
}
}
private AABBoid parent;
private final Vec3 translation = new Vec3();
private final TranslatedAABBWall[] walls = new TranslatedAABBWall[BlockFace.BLOCK_FACE_COUNT];
{
for (int id = 0; id < walls.length; ++id) {
walls[id] = new TranslatedAABBWall(id);
}
}
public TranslatedAABB(AABBoid parent, float tx, float ty, float tz) {
setParent(parent);
setTranslation(tx, ty, tz);
}
public TranslatedAABB(AABBoid parent, Vec3 translation) {
this(parent, translation.x, translation.y, translation.z);
}
public TranslatedAABB() {
this(null, 0, 0, 0);
}
@Override
public void setOrigin(Vec3 origin) {
Vec3 v = Vectors.grab3().set(origin).sub(translation);
parent.setOrigin(v);
Vectors.release(v);
}
@Override
public void moveOrigin(Vec3 displacement) {
parent.moveOrigin(displacement);
}
@Override
public void getOrigin(Vec3 output) {
parent.getOrigin(output);
output.add(translation);
}
@Override
public void getSize(Vec3 output) {
parent.getSize(output);
}
@Override
public Wall getWall(int faceId) {
return walls[faceId];
}
public AABBoid getParent() {
return parent;
}
public void setParent(AABBoid parent) {
this.parent = parent;
}
public Vec3 getTranslation() {
return translation;
}
public void setTranslation(Vec3 translation) {
setTranslation(translation.x, translation.y, translation.z);
}
public void setTranslation(float tx, float ty, float tz) {
this.translation.set(tx, ty, tz);
}
}

View File

@ -0,0 +1,12 @@
package ru.windcorp.progressia.common.collision;
import glm.vec._3.Vec3;
public interface Wall {
void getOrigin(Vec3 output);
void getWidth(Vec3 output);
void getHeight(Vec3 output);
}

View File

@ -0,0 +1,94 @@
package ru.windcorp.progressia.common.collision;
import java.util.ArrayList;
import java.util.Collection;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.LowOverheadCache;
import ru.windcorp.progressia.common.world.WorldData;
public class WorldCollisionHelper {
private final Collideable collideable = new Collideable() {
@Override
public boolean onCollision(Collideable other) {
return false;
}
@Override
public void moveAsCollideable(Vec3 displacement) {
// Ignore
assert displacement.length() < 1e-3f;
}
@Override
public CollisionModel getCollisionModel() {
return WorldCollisionHelper.this.model;
}
@Override
public float getCollisionMass() {
return Float.POSITIVE_INFINITY;
}
@Override
public void getCollideableVelocity(Vec3 output) {
output.set(0);
}
@Override
public void changeVelocityOnCollision(Vec3 velocityChange) {
// Ignore
assert velocityChange.length() < 1e-3f;
}
};
private final Collection<TranslatedAABB> activeBlockModels = new ArrayList<>();
private final CollisionModel model = new CompoundCollisionModel(activeBlockModels);
private final LowOverheadCache<TranslatedAABB> blockModelCache = new LowOverheadCache<>(TranslatedAABB::new);
/**
* Changes the state of this helper's {@link #getCollideable()} so it is ready to adequately handle
* collisions with the {@code collideable} that might happen in the next {@code maxTime} seconds.
* This helper is only valid for checking collisions with the given Collideable and only within
* the given time limit.
* @param collideable the {@link Collideable} that collisions will be checked against
* @param maxTime maximum collision time
*/
public void tuneToCollideable(WorldData world, Collideable collideable, float maxTime) {
activeBlockModels.forEach(blockModelCache::release);
activeBlockModels.clear();
CollisionPathComputer.forEveryBlockInCollisionPath(
collideable,
maxTime,
v -> addModel(world.getCollisionModelOfBlock(v), v)
);
}
private void addModel(CollisionModel model, Vec3i pos) {
if (model == null) {
// Ignore
} else if (model instanceof AABBoid) {
addAABBoidModel((AABBoid) model, pos);
} else if (model instanceof CompoundCollisionModel) {
for (CollisionModel subModel : ((CompoundCollisionModel) model).getModels()) {
addModel(subModel, pos);
}
} else {
throw new RuntimeException("not supported");
}
}
private void addAABBoidModel(AABBoid model, Vec3i pos) {
TranslatedAABB translator = blockModelCache.grab();
translator.setParent(model);
translator.setTranslation(pos.x, pos.y, pos.z);
activeBlockModels.add(translator);
}
public Collideable getCollideable() {
return collideable;
}
}

View File

@ -2,26 +2,25 @@ package ru.windcorp.progressia.common.collision.colliders;
import glm.mat._3.Mat3;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.CollisionWall;
import ru.windcorp.progressia.common.collision.*;
import ru.windcorp.progressia.common.collision.colliders.Collider.ColliderWorkspace;
import ru.windcorp.progressia.common.collision.colliders.Collider.Collision;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.block.BlockFace;
class AABBWithAABBCollider {
class AABBoidCollider {
static Collider.Collision computeModelCollision(
Collideable aBody, Collideable bBody,
AABB aModel, AABB bModel,
AABBoid aModel, AABBoid bModel,
float tickLength,
ColliderWorkspace workspace
) {
Collideable obstacleBody = bBody;
Collideable colliderBody = aBody;
AABB obstacleModel = bModel;
AABB colliderModel = aModel;
AABBoid obstacleModel = bModel;
AABBoid colliderModel = aModel;
Collision result = null;
@ -32,7 +31,8 @@ class AABBWithAABBCollider {
computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody);
// For every wall of collision space
for (CollisionWall wall : originCollisionSpace.getFaces()) {
for (int i = 0; i < BlockFace.BLOCK_FACE_COUNT; ++i) {
Wall wall = originCollisionSpace.getWall(i);
Collision collision = computeWallCollision(
wall, colliderModel,
@ -80,12 +80,21 @@ class AABBWithAABBCollider {
Vectors.release(colliderVelocity);
}
private static AABB createOriginCollisionSpace(AABB obstacle, AABB collider, AABB output) {
output.setOrigin(obstacle.getOrigin());
private static AABB createOriginCollisionSpace(AABBoid obstacle, AABBoid collider, AABB output) {
Vec3 obstacleOrigin = Vectors.grab3();
Vec3 obstacleSize = Vectors.grab3();
Vec3 colliderSize = Vectors.grab3();
Vec3 size = Vectors.grab3().set(obstacle.getSize()).add(collider.getSize());
output.setSize(size);
Vectors.release(size);
obstacle.getOrigin(obstacleOrigin);
output.setOrigin(obstacleOrigin);
obstacle.getSize(obstacleSize);
collider.getSize(colliderSize);
output.setSize(obstacleSize.add(colliderSize));
Vectors.release(obstacleOrigin);
Vectors.release(obstacleSize);
Vectors.release(colliderSize);
return output;
}
@ -134,27 +143,34 @@ class AABBWithAABBCollider {
* If all conditions are satisfied, then the moment of impact is t0 + t.
*/
private static Collision computeWallCollision(
CollisionWall obstacleWall,
AABB colliderModel,
Wall obstacleWall,
AABBoid colliderModel,
Vec3 collisionVelocity,
float tickLength, ColliderWorkspace workspace,
Collideable aBody, Collideable bBody
) {
Vec3 w = obstacleWall.getWidth();
Vec3 h = obstacleWall.getHeight();
Vec3 w = Vectors.grab3();
Vec3 h = Vectors.grab3();
Vec3 v = Vectors.grab3();
Mat3 m = Matrices.grab3(); // The matrix [w h -v]
Vec3 r = Vectors.grab3();
Vec3 r_line = Vectors.grab3();
Vec3 r_wall = Vectors.grab3();
Vec3 xyt = Vectors.grab3();
try {
obstacleWall.getWidth(w);
obstacleWall.getHeight(h);
v.set(collisionVelocity);
if (isExiting(v, w, h)) {
return null;
}
r.set(colliderModel.getOrigin()).sub(obstacleWall.getOrigin());
obstacleWall.getOrigin(r_wall);
colliderModel.getOrigin(r_line);
r.set(r_line).sub(r_wall);
m.c0(w).c1(h).c2(v.negate());
if (Math.abs(m.det()) < 1e-6) {
@ -179,9 +195,13 @@ class AABBWithAABBCollider {
return workspace.grab().set(aBody, bBody, obstacleWall, t);
} finally {
Vectors.release(w);
Vectors.release(h);
Vectors.release(v);
Vectors.release(r);
Matrices.release(m);
Vectors.release(r);
Vectors.release(r_line);
Vectors.release(r_wall);
Vectors.release(xyt);
}
}
@ -193,6 +213,6 @@ class AABBWithAABBCollider {
return result;
}
private AABBWithAABBCollider() {}
private AABBoidCollider() {}
}

View File

@ -6,14 +6,10 @@ import java.util.List;
import org.apache.logging.log4j.LogManager;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.CollisionClock;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.collision.CollisionWall;
import ru.windcorp.progressia.common.collision.CompoundCollisionModel;
import ru.windcorp.progressia.common.collision.*;
import ru.windcorp.progressia.common.util.LowOverheadCache;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.WorldData;
public class Collider {
@ -21,7 +17,7 @@ public class Collider {
public static void performCollisions(
List<? extends Collideable> colls,
CollisionClock clock,
WorldData world,
float tickLength,
ColliderWorkspace workspace
) {
@ -37,12 +33,12 @@ public class Collider {
return;
}
Collision firstCollision = getFirstCollision(colls, tickLength, workspace);
Collision firstCollision = getFirstCollision(colls, tickLength, world, workspace);
if (firstCollision == null) {
break;
} else {
collide(firstCollision, colls, clock, tickLength, workspace);
collide(firstCollision, colls, world, tickLength, workspace);
workspace.release(firstCollision);
collisionCount++;
@ -50,45 +46,49 @@ public class Collider {
}
}
advanceTime(colls, clock, tickLength);
advanceTime(colls, world, tickLength);
}
private static Collision getFirstCollision(
List<? extends Collideable> colls,
float tickLength,
WorldData world,
ColliderWorkspace workspace
) {
Collision result = null;
Collideable worldColl = workspace.worldCollisionHelper.getCollideable();
// For every pair of colls
for (int i = 0; i < colls.size(); ++i) {
Collideable a = colls.get(i);
tuneWorldCollisionHelper(a, tickLength, world, workspace);
result = workspace.updateLatestCollision(
result,
getCollision(a, worldColl, tickLength, workspace)
);
for (int j = i + 1; j < colls.size(); ++j) {
Collideable b = colls.get(j);
Collision collision = getCollision(a, b, tickLength, workspace);
// Update result
if (collision != null) {
Collision second;
if (result == null || collision.time < result.time) {
second = result;
result = collision;
} else {
second = collision;
}
// Release Collision that is no longer used
if (second != null) workspace.release(second);
}
result = workspace.updateLatestCollision(result, collision);
}
}
return result;
}
private static void tuneWorldCollisionHelper(
Collideable coll,
float tickLength,
WorldData world,
ColliderWorkspace workspace
) {
WorldCollisionHelper wch = workspace.worldCollisionHelper;
wch.tuneToCollideable(world, coll, tickLength);
}
static Collision getCollision(
Collideable a,
Collideable b,
@ -108,10 +108,10 @@ public class Collider {
float tickLength,
ColliderWorkspace workspace
) {
if (aModel instanceof AABB && bModel instanceof AABB) {
return AABBWithAABBCollider.computeModelCollision(
if (aModel instanceof AABBoid && bModel instanceof AABBoid) {
return AABBoidCollider.computeModelCollision(
aBody, bBody,
(AABB) aModel, (AABB) bModel,
(AABBoid) aModel, (AABBoid) bModel,
tickLength,
workspace
);
@ -144,11 +144,11 @@ public class Collider {
Collision collision,
Collection<? extends Collideable> colls,
CollisionClock clock,
WorldData world,
float tickLength,
ColliderWorkspace workspace
) {
advanceTime(colls, clock, collision.time);
advanceTime(colls, world, collision.time);
boolean doNotHandle = false;
@ -237,7 +237,7 @@ public class Collider {
Vec3 du_a = Vectors.grab3();
Vec3 du_b = Vectors.grab3();
n.set(collision.wall.getWidth()).cross(collision.wall.getHeight()).normalize();
n.set(collision.wallWidth).cross(collision.wallHeight).normalize();
collision.a.getCollideableVelocity(v_a);
collision.b.getCollideableVelocity(v_b);
@ -306,10 +306,10 @@ public class Collider {
private static void advanceTime(
Collection<? extends Collideable> colls,
CollisionClock clock,
WorldData world,
float step
) {
clock.advanceTime(step);
world.advanceTime(step);
Vec3 tmp = Vectors.grab3();
@ -328,6 +328,8 @@ public class Collider {
new LowOverheadCache<>(Collision::new);
AABB dummyAABB = new AABB(0, 0, 0, 1, 1, 1);
WorldCollisionHelper worldCollisionHelper = new WorldCollisionHelper();
Collision grab() {
return collisionCache.grab();
@ -337,12 +339,35 @@ public class Collider {
collisionCache.release(object);
}
Collision updateLatestCollision(Collision a, Collision b) {
if (a == null) {
return b; // may be null
} else if (b == null) {
return a;
}
Collision first, second;
if (a.time > b.time) {
first = b;
second = a;
} else {
first = a;
second = b;
}
release(second);
return first;
}
}
static class Collision {
public Collideable a;
public Collideable b;
public final CollisionWall wall = new CollisionWall(0, 0, 0, 0, 0, 0, 0, 0, 0);
public final Vec3 wallWidth = new Vec3();
public final Vec3 wallHeight = new Vec3();
/**
* Time offset from the start of the tick.
@ -350,12 +375,15 @@ public class Collider {
*/
public float time;
public Collision set(Collideable a, Collideable b, CollisionWall wall, float time) {
public Collision set(
Collideable a, Collideable b,
Wall wall,
float time
) {
this.a = a;
this.b = b;
this.wall.getOrigin().set(wall.getOrigin());
this.wall.getWidth().set(wall.getWidth());
this.wall.getHeight().set(wall.getHeight());
wall.getWidth(wallWidth);
wall.getHeight(wallHeight);
this.time = time;
return this;

View File

@ -30,6 +30,19 @@ import glm.vec._4.i.Vec4i;
*/
public class Vectors {
public static final Vec2 ZERO_2 = new Vec2 (0, 0);
public static final Vec2i ZERO_2i = new Vec2i(0, 0);
public static final Vec3 ZERO_3 = new Vec3 (0, 0, 0);
public static final Vec3i ZERO_3i = new Vec3i(0, 0, 0);
public static final Vec4 ZERO_4 = new Vec4 (0, 0, 0, 0);
public static final Vec4i ZERO_4i = new Vec4i(0, 0, 0, 0);
public static final Vec2 UNIT_2 = new Vec2 (1, 1);
public static final Vec2i UNIT_2i = new Vec2i(1, 1);
public static final Vec3 UNIT_3 = new Vec3 (1, 1, 1);
public static final Vec3i UNIT_3i = new Vec3i(1, 1, 1);
public static final Vec4 UNIT_4 = new Vec4 (1, 1, 1, 1);
public static final Vec4i UNIT_4i = new Vec4i(1, 1, 1, 1);
private static final LowOverheadCache<Vec3i> VEC3IS =
new LowOverheadCache<>(Vec3i::new);

View File

@ -80,7 +80,7 @@ public class ChunkData {
TileData flowers = TileDataRegistry.getInstance().get("Test:YellowFlowers");
TileData sand = TileDataRegistry.getInstance().get("Test:Sand");
Vec3i aPoint = new Vec3i(5, 0, BLOCKS_PER_CHUNK + BLOCKS_PER_CHUNK/2);
Vec3i aPoint = new Vec3i(5, 0, BLOCKS_PER_CHUNK + BLOCKS_PER_CHUNK/2).sub(getPosition());
Vec3i pos = new Vec3i();
for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) {
@ -132,18 +132,28 @@ public class ChunkData {
}
}
EntityData javapony = EntityDataRegistry.getInstance().create("Test:Javapony");
javapony.setEntityId(0x42);
javapony.setPosition(new Vec3(-6, -6, 20));
javapony.setDirection(new Vec2(
(float) Math.toRadians(40), (float) Math.toRadians(45)
));
getEntities().add(javapony);
EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie");
statie.setEntityId(0xDEADBEEF);
statie.setPosition(new Vec3(0, 15, 16));
getEntities().add(statie);
if (!getPosition().any()) {
// EntityData javapony = EntityDataRegistry.getInstance().create("Test:Javapony");
// javapony.setEntityId(0x42);
// javapony.setPosition(new Vec3(-6, -6, 20));
// javapony.setDirection(new Vec2(
// (float) Math.toRadians(40), (float) Math.toRadians(45)
// ));
// getEntities().add(javapony);
EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
player.setEntityId(0x42);
player.setPosition(new Vec3(-6, -6, 20));
player.setDirection(new Vec2(
(float) Math.toRadians(40), (float) Math.toRadians(45)
));
getEntities().add(player);
EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie");
statie.setEntityId(0xDEADBEEF);
statie.setPosition(new Vec3(0, 15, 16));
getEntities().add(statie);
}
}
public BlockData getBlock(Vec3i posInChunk) {

View File

@ -24,8 +24,10 @@ import glm.vec._3.i.Vec3i;
import gnu.trove.impl.sync.TSynchronizedLongObjectMap;
import gnu.trove.map.TLongObjectMap;
import gnu.trove.map.hash.TLongObjectHashMap;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.util.CoordinatePacker;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class WorldData {
@ -42,6 +44,8 @@ public class WorldData {
private final Collection<EntityData> entities =
Collections.unmodifiableCollection(entitiesById.valueCollection());
private float time = 0;
public WorldData() {
final int size = 1;
@ -96,4 +100,25 @@ public class WorldData {
return entities;
}
public float getTime() {
return time;
}
public void advanceTime(float change) {
this.time += change;
}
public CollisionModel getCollisionModelOfBlock(Vec3i blockInWorld) {
ChunkData chunk = getChunkByBlock(blockInWorld);
if (chunk == null) return null;
Vec3i blockInChunk = Vectors.grab3i();
Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk);
BlockData block = chunk.getBlock(blockInChunk);
Vectors.release(blockInChunk);
if (block == null) return null;
return block.getCollisionModel();
}
}

View File

@ -17,6 +17,8 @@
*******************************************************************************/
package ru.windcorp.progressia.common.world.block;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.util.Namespaced;
public class BlockData extends Namespaced {
@ -24,5 +26,9 @@ public class BlockData extends Namespaced {
public BlockData(String namespace, String name) {
super(namespace, name);
}
public CollisionModel getCollisionModel() {
return AABB.UNIT_CUBE;
}
}

View File

@ -1,35 +0,0 @@
package ru.windcorp.progressia.test;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.Shapes;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.collision.CompoundCollisionModel;
public class AABBRenderer {
private static final Shape CUBE = new Shapes.PppBuilder(WorldRenderProgram.getDefault(), (Texture) null).setColorMultiplier(1.0f, 0.7f, 0.2f).create();
public static void renderAABB(AABB aabb, ShapeRenderHelper helper) {
helper.pushTransform().translate(aabb.getOrigin()).scale(aabb.getSize());
CUBE.render(helper);
helper.popTransform();
}
public static void renderAABBsInCompound(
CompoundCollisionModel model,
ShapeRenderHelper helper
) {
for (CollisionModel part : model.getModels()) {
if (part instanceof CompoundCollisionModel) {
renderAABBsInCompound((CompoundCollisionModel) part, helper);
} else if (part instanceof AABB) {
renderAABB((AABB) part, helper);
}
}
}
}

View File

@ -0,0 +1,61 @@
package ru.windcorp.progressia.test;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.Shapes;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.common.collision.AABBoid;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.collision.CompoundCollisionModel;
import ru.windcorp.progressia.common.util.Vectors;
public class CollisionModelRenderer {
private static final Shape CUBE = new Shapes.PppBuilder(WorldRenderProgram.getDefault(), (Texture) null).setColorMultiplier(1.0f, 0.7f, 0.2f).create();
private static final Shape CUBE_GRAY = new Shapes.PppBuilder(WorldRenderProgram.getDefault(), (Texture) null).setColorMultiplier(0.5f, 0.5f, 0.5f).create();
public static void renderCollisionModel(CollisionModel model, ShapeRenderHelper helper) {
if (model instanceof AABBoid) {
renderAABBoid((AABBoid) model, helper);
} else if (model instanceof CompoundCollisionModel) {
renderCompound((CompoundCollisionModel) model, helper);
} else {
// Ignore silently
}
}
private static void renderAABBoid(AABBoid aabb, ShapeRenderHelper helper) {
Mat4 mat = helper.pushTransform();
Vec3 tmp = Vectors.grab3();
aabb.getOrigin(tmp);
mat.translate(tmp);
aabb.getSize(tmp);
mat.scale(tmp);
Vectors.release(tmp);
CUBE.render(helper);
helper.popTransform();
}
private static void renderCompound(
CompoundCollisionModel model,
ShapeRenderHelper helper
) {
for (CollisionModel part : model.getModels()) {
renderCollisionModel(part, helper);
}
}
public static void renderBlock(Vec3i coords, ShapeRenderHelper helper) {
helper.pushTransform().translate(coords.x, coords.y, coords.z);
CUBE_GRAY.render(helper);
helper.popTransform();
}
}

View File

@ -0,0 +1,155 @@
/*******************************************************************************
* Progressia
* Copyright (C) 2020 Wind Corporation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*******************************************************************************/
package ru.windcorp.progressia.test;
import java.util.ArrayList;
import java.util.Collection;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.Panel;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
public class LayerTestGUI extends GUILayer {
public LayerTestGUI() {
super("LayerTestGui", new LayoutAlign(0, 1, 5));
Panel panel = new Panel("ControlDisplays", new LayoutVertical(5));
Collection<Label> labels = new ArrayList<>();
panel.addChild(new Label(
"IsFlyingDisplay", new Font().withColor(0x37A3E6).deriveShadow(),
() -> String.format("Flying: %5s (Space bar x2)", TestPlayerControls.getInstance().isFlying())
));
panel.addChild(new Label(
"IsMouseCapturedDisplay", new Font().withColor(0x37A3E6).deriveShadow(),
() -> String.format("Mouse captured: %5s (esc)", TestPlayerControls.getInstance().isMouseCaptured())
));
panel.addChild(new Label(
"CameraModeDisplay", new Font().withColor(0x37A3E6).deriveShadow(),
() -> String.format("Camera mode: %5d (F5)", ClientState.getInstance().getCamera().getCurrentModeIndex())
));
panel.addChild(new Label(
"GravityModeDisplay", new Font().withColor(0x37A3E6).deriveShadow(),
() -> String.format("Gravity: %9s (G)", TestPlayerControls.getInstance().useMinecraftGravity() ? "Minecraft" : "Realistic")
));
panel.getChildren().forEach(c -> labels.add((Label) c));
TestPlayerControls.getInstance().setUpdateCallback(() -> labels.forEach(Label::update));
getRoot().addChild(panel);
}
// private static class DebugComponent extends Component {
// private final int color;
//
// public DebugComponent(String name, Vec2i size, int color) {
// super(name);
// this.color = color;
//
// setPreferredSize(size);
//
// addListener(new Object() {
// @Subscribe
// public void onHoverChanged(HoverEvent e) {
// requestReassembly();
// }
// });
//
// addListener(KeyEvent.class, this::onClicked);
// }
//
// private boolean onClicked(KeyEvent event) {
// if (!event.isMouse()) {
// return false;
// } else if (event.isPress() && event.isLeftMouseButton()) {
// System.out.println("You pressed a Component!");
// }
// return true;
// }
//
// @Override
// protected void assembleSelf(RenderTarget target) {
// target.fill(getX(), getY(), getWidth(), getHeight(), Colors.BLACK);
//
// target.fill(
// getX() + 2, getY() + 2,
// getWidth() - 4, getHeight() - 4,
// isHovered() ? Colors.DEBUG_YELLOW : color
// );
// }
// }
//
// public LayerTestGUI() {
// super("LayerTestGui", new LayoutAlign(1, 0.75, 5));
//
// Panel panel = new Panel("Alex", new LayoutVertical(5));
//
// panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), 0x44FF44));
//
// Component charlie = new DebugComponent("Charlie", null, 0x222222);
// charlie.setLayout(new LayoutVertical(5));
//
// //Debug
// Localizer.getInstance().setLanguage("ru-RU");
// MutableString epsilon = new MutableStringLocalized("Epsilon")
// .addListener(() -> ((Label)charlie.getChild(0)).update()).format(34, "thirty-four");
// // These two are swapped in code due to a bug in layouts, fixing ATM
// charlie.addChild(
// new Label(
// "Delta",
// new Font().withColor(0xCCBB44).deriveShadow().deriveBold(),
// "Пре-альфа!"
// )
// );
// charlie.addChild(
// new Label(
// "Epsilon",
// new Font().withColor(0x4444BB).deriveItalic(),
// () -> epsilon.get().concat("\u269b")
// )
// );
// panel.addChild(charlie);
//
//
// charlie.addListener(KeyEvent.class, e -> {
// if(e.isPress() && e.getKey() == GLFW.GLFW_KEY_L) {
// Localizer localizer = Localizer.getInstance();
// if (localizer.getLanguage().equals("ru-RU")) {
// localizer.setLanguage("en-US");
// } else {
// localizer.setLanguage("ru-RU");
// }
// return true;
// } return false;
// });
// charlie.setFocusable(true);
// charlie.takeFocus();
//
// getRoot().addChild(panel);
// }
}

View File

@ -14,6 +14,7 @@ import ru.windcorp.progressia.client.world.block.*;
import ru.windcorp.progressia.client.world.entity.*;
import ru.windcorp.progressia.client.world.tile.*;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.collision.CollisionModel;
import ru.windcorp.progressia.common.comms.controls.*;
import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory;
import ru.windcorp.progressia.common.world.ChunkData;
@ -41,7 +42,12 @@ public class TestContent {
}
private static void registerBlocks() {
register(new BlockData("Test", "Air"));
register(new BlockData("Test", "Air") {
@Override
public CollisionModel getCollisionModel() {
return null;
}
});
register(new BlockRenderNone("Test", "Air"));
register(new BlockLogic("Test", "Air"));
@ -81,9 +87,14 @@ public class TestContent {
}
private static void registerEntities() {
registerEntityData("Test", "Javapony", e -> e.setCollisionModel(new AABB(0, 0, -0.05f, 0.75f, 0.75f, 1.2f)));
register(new TestEntityRenderJavapony());
register(new EntityLogic("Test", "Javapony"));
// registerEntityData("Test", "Javapony", e -> e.setCollisionModel(new AABB(0, 0, -0.05f, 0.75f, 0.75f, 1.2f)));
// register(new TestEntityRenderJavapony());
// register(new EntityLogic("Test", "Javapony"));
float scale = 1.8f / 8;
registerEntityData("Test", "Player", e -> e.setCollisionModel(new AABB(0, 0, 4*scale, 0.75f, 0.75f, 1.8f)));
register(new TestEntityRenderHuman());
register(new EntityLogic("Test", "Player"));
register("Test", "Statie", TestEntityDataStatie::new);
register(new TestEntityRenderStatie());
@ -92,7 +103,7 @@ public class TestContent {
private static void regsiterControls() {
ControlDataRegistry.getInstance().register(new ControlData("Test", "Switch000"));
ControlTriggerRegistry.getInstance().register(new ControlTriggerOnKeyPress("Test", "Switch000", new KeyMatcher(GLFW.GLFW_KEY_G, new int[0], 0)::matches));
ControlTriggerRegistry.getInstance().register(new ControlTriggerOnKeyPress("Test", "Switch000", new KeyMatcher(GLFW.GLFW_KEY_H, new int[0], 0)::matches));
ControlLogicRegistry.getInstance().register(new ControlLogic("Test", "Switch000") {
@Override
public void apply(Server server, PacketControl packet, Client client) {

View File

@ -1,7 +1,6 @@
package ru.windcorp.progressia.test;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.collision.CompoundCollisionModel;
import ru.windcorp.progressia.common.state.IntStateField;
import ru.windcorp.progressia.common.world.entity.EntityData;
@ -12,10 +11,7 @@ public class TestEntityDataStatie extends EntityData {
public TestEntityDataStatie() {
super("Test", "Statie");
setCollisionModel(new CompoundCollisionModel(
new AABB(0, 0, 0, 1, 1, 1 ),
new AABB(0, 0, 0.7f, 0.6f, 0.6f, 0.6f)
));
setCollisionModel(new AABB(0, 0, 0, 1, 1, 1));
setSizeNow(16);
}

View File

@ -0,0 +1,164 @@
package ru.windcorp.progressia.test;
import static java.lang.Math.toRadians;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.LambdaModel;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
import ru.windcorp.progressia.client.graphics.model.StaticModel;
import ru.windcorp.progressia.client.graphics.texture.ComplexTexture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.entity.HumanoidModel;
import ru.windcorp.progressia.client.world.entity.EntityRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.common.util.FloatMathUtils;
import ru.windcorp.progressia.common.world.entity.EntityData;
import static java.lang.Math.*;
public class TestEntityRenderHuman extends EntityRender {
private static final float SECOND_LAYER_OFFSET = 1 / 12f;
private final Renderable body;
private final Renderable head;
private final Renderable leftArm;
private final Renderable rightArm;
private final Renderable leftLeg;
private final Renderable rightLeg;
public TestEntityRenderHuman() {
super("Test", "Player");
ComplexTexture texture = new ComplexTexture(
EntityRenderRegistry.getEntityTexture("pyotr"),
16, 16
);
this.body = createBody(texture);
this.head = createHead(texture);
this.leftArm = createLimb(texture, 8, 0, 12, 0, true, true);
this.rightArm = createLimb(texture, 10, 8, 10, 4, true, false);
this.leftLeg = createLimb(texture, 4, 0, 0, 0, false, true);
this.rightLeg = createLimb(texture, 0, 8, 0, 4, false, false);
}
private Renderable createBody(ComplexTexture texture) {
return createLayeredCuboid(
texture,
4, 8,
4, 4,
2, 3, 1,
-0.5f, -1, 3,
1, 2, 3
);
}
private Renderable createHead(ComplexTexture texture) {
return createLayeredCuboid(
texture,
0, 12,
8, 12,
2, 2, 2,
-1, -1, 0,
2, 2, 2
);
}
private Renderable createLimb(
ComplexTexture texture,
int tx, int ty,
int tx2, int ty2,
boolean isArm, boolean isLeft
) {
Renderable model = createLayeredCuboid(
texture,
tx, ty,
tx2, ty2,
1, 3, 1,
-0.5f, -0.5f, isArm ? -2.5f : -3f,
1, 1, 3
);
if (isArm) {
return LambdaModel.animate(
model,
mat -> {
double phase = GraphicsInterface.getTime() + (isLeft ? 0 : Math.PI / 3);
mat.rotateX((isLeft ? +1 : -1) * 1/40f * (sin(phase) + 1));
mat.rotateY(1/20f * sin(Math.PI / 3 * phase));
}
);
} else {
return model;
}
}
private Renderable createLayeredCuboid(
ComplexTexture texture,
int tx, int ty,
int tx2, int ty2,
int tw, int th, int td,
float ox, float oy, float oz,
float sx, float sy, float sz
) {
WorldRenderProgram program = WorldRenderProgram.getDefault();
StaticModel.Builder b = StaticModel.builder();
// First layer
b.addPart(new PppBuilder(
program,
texture.getCuboidTextures(tx, ty, tw, th, td)
).setOrigin(ox, oy, oz).setSize(sx, sy, sz).create());
ox -= SECOND_LAYER_OFFSET;
oy -= SECOND_LAYER_OFFSET;
oz -= SECOND_LAYER_OFFSET;
sx += SECOND_LAYER_OFFSET * 2;
sy += SECOND_LAYER_OFFSET * 2;
sz += SECOND_LAYER_OFFSET * 2;
// Second layer
b.addPart(new PppBuilder(
program,
texture.getCuboidTextures(tx2, ty2, tw, th, td)
).setOrigin(ox, oy, oz).setSize(sx, sy, sz).create());
return new StaticModel(b);
}
@Override
public EntityRenderable createRenderable(EntityData entity) {
return new HumanoidModel(
entity,
new HumanoidModel.Body(body),
new HumanoidModel.Head(
head, new Vec3(0, 0, 6), 70, 25, new Vec3(1.2f, 0, 1.5f)
),
new HumanoidModel.Arm(
leftArm, new Vec3(0, +1.5f, 3 + 3 - 0.5f), 0.0f
),
new HumanoidModel.Arm(
rightArm, new Vec3(0, -1.5f, 3 + 3 - 0.5f), FloatMathUtils.PI_F
),
new HumanoidModel.Leg(
leftLeg, new Vec3(0, +0.5f, 3), FloatMathUtils.PI_F
),
new HumanoidModel.Leg(
rightLeg, new Vec3(0, -0.5f, 3), 0.0f
),
1.8f / (3 + 3 + 2)
)
.setWalkingArmSwing((float) toRadians(30))
.setWalkingLegSwing((float) toRadians(50))
.setWalkingFrequency(0.15f / 60.0f);
}
}

View File

@ -0,0 +1,270 @@
package ru.windcorp.progressia.test;
import org.lwjgl.glfw.GLFW;
import glm.Glm;
import glm.mat._3.Mat3;
import glm.vec._2.Vec2;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.FloatMathUtils;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
public class TestPlayerControls {
private static final TestPlayerControls INSTANCE = new TestPlayerControls();
public static TestPlayerControls getInstance() {
return INSTANCE;
}
private TestPlayerControls() {}
private static final double MODE_SWITCH_MAX_DELAY = 100 * Units.MILLISECONDS;
private static final double MIN_JUMP_DELAY = 200 * Units.MILLISECONDS;
// Horizontal and vertical max control speed when flying
private static final float FLYING_SPEED = 6.0f * Units.METERS_PER_SECOND;
// (0; 1], 1 is instant change, 0 is no control authority
private static final float FLYING_CONTROL_AUTHORITY = 0.05f;
// Horizontal and vertical max control speed when walking
private static final float WALKING_SPEED = 4.0f * Units.METERS_PER_SECOND;
// (0; 1], 1 is instant change, 0 is no control authority
private static final float WALKING_CONTROL_AUTHORITY = 0.1f;
// Vertical velocity instantly add to player when they jump
private static final float JUMP_VELOCITY = 5f * Units.METERS_PER_SECOND;
private boolean isFlying = true;
private int movementForward = 0;
private int movementRight = 0;
private int movementUp = 0;
private double lastSpacePress = Double.NEGATIVE_INFINITY;
private boolean captureMouse = true;
private boolean useMinecraftGravity = false;
private Runnable updateCallback = null;
public void applyPlayerControls() {
if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) {
return;
}
EntityData player = getPlayer();
Mat3 angMat = Matrices.grab3();
angMat.identity().rotateZ(player.getYaw());
Vec3 movement = Vectors.grab3();
movement.set(movementForward, -movementRight, 0);
if (movementForward != 0 && movementRight != 0) {
movement.normalize();
}
angMat.mul_(movement); // bug in jglm, .mul() and mul_() are swapped
if (isFlying) {
movement.z = movementUp;
movement.mul(FLYING_SPEED);
movement.sub(player.getVelocity());
movement.mul(FLYING_CONTROL_AUTHORITY);
} else {
movement.mul(WALKING_SPEED);
movement.sub(player.getVelocity());
movement.mul(WALKING_CONTROL_AUTHORITY);
movement.z = 0;
}
player.getVelocity().add(movement);
Matrices.release(angMat);
Vectors.release(movement);
}
public void handleInput(Input input) {
if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) {
return;
}
InputEvent event = input.getEvent();
if (event instanceof KeyEvent) {
if (onKeyEvent((KeyEvent) event)) {
input.consume();
}
} else if (event instanceof CursorMoveEvent) {
onMouseMoved((CursorMoveEvent) event);
input.consume();
}
}
private boolean onKeyEvent(KeyEvent event) {
if (event.isRepeat()) return false;
int multiplier = event.isPress() ? 1 : -1;
switch (event.getKey()) {
case GLFW.GLFW_KEY_W:
movementForward += +1 * multiplier;
break;
case GLFW.GLFW_KEY_S:
movementForward += -1 * multiplier;
break;
case GLFW.GLFW_KEY_A:
movementRight += -1 * multiplier;
break;
case GLFW.GLFW_KEY_D:
movementRight += +1 * multiplier;
break;
case GLFW.GLFW_KEY_SPACE:
handleSpace(multiplier);
break;
case GLFW.GLFW_KEY_LEFT_SHIFT:
handleShift(multiplier);
break;
case GLFW.GLFW_KEY_ESCAPE:
if (!event.isPress()) return false;
handleEscape();
break;
case GLFW.GLFW_KEY_F5:
if (!event.isPress()) return false;
handleCameraMode();
break;
case GLFW.GLFW_KEY_G:
if (!event.isPress()) return false;
handleGravitySwitch();
break;
default:
return false;
}
return true;
}
private void handleSpace(int multiplier) {
boolean isPressed = multiplier > 0;
double timeSinceLastSpacePress = GraphicsInterface.getTime() - lastSpacePress;
if (isPressed && timeSinceLastSpacePress < MODE_SWITCH_MAX_DELAY) {
isFlying = !isFlying;
updateGUI();
movementUp = +1;
} else {
if (isFlying) {
movementUp += +1 * multiplier;
} else {
if (isPressed && timeSinceLastSpacePress > MIN_JUMP_DELAY) {
jump();
}
}
}
lastSpacePress = GraphicsInterface.getTime();
}
private void jump() {
getPlayer().getVelocity().add(0, 0, JUMP_VELOCITY * (useMinecraftGravity ? 2 : 1));
}
private void handleShift(int multiplier) {
if (isFlying) {
movementUp += -1 * multiplier;
}
}
private void handleEscape() {
if (captureMouse) {
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
} else {
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
}
captureMouse = !captureMouse;
updateGUI();
}
private void handleCameraMode() {
if (ClientState.getInstance().getCamera().hasAnchor()) {
ClientState.getInstance().getCamera().selectNextMode();
updateGUI();
}
}
private void handleGravitySwitch() {
useMinecraftGravity = !useMinecraftGravity;
updateGUI();
}
private void onMouseMoved(CursorMoveEvent event) {
if (!captureMouse) return;
final float yawScale = -0.002f;
final float pitchScale = yawScale;
EntityData player = getPlayer();
normalizeAngles(player.getDirection().add(
(float) (event.getChangeX() * yawScale),
(float) (event.getChangeY() * pitchScale)
));
}
private void normalizeAngles(Vec2 dir) {
// Normalize yaw
dir.x = FloatMathUtils.normalizeAngle(dir.x);
// Clamp pitch
dir.y = Glm.clamp(
dir.y, -FloatMathUtils.PI_F/2, +FloatMathUtils.PI_F/2
);
}
private EntityData getPlayer() {
return ClientState.getInstance().getLocalPlayer();
}
public void setUpdateCallback(Runnable updateCallback) {
this.updateCallback = updateCallback;
}
private void updateGUI() {
if (this.updateCallback != null) {
this.updateCallback.run();
}
}
public boolean isFlying() {
return isFlying;
}
public boolean isMouseCaptured() {
return captureMouse;
}
public boolean useMinecraftGravity() {
return useMinecraftGravity;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB