polly-b-gone/player.cpp
2012-12-14 09:47:48 -08:00

626 lines
17 KiB
C++

// -*- C++ -*-
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <iostream>
#include <math.h>
#include <stdio.h>
#include <vector>
#include "material.h"
#include "model.h"
#include "physics/constraint.h"
#include "physics/force.h"
#include "physics/particle.h"
#include "physics/shape.h"
#include "physics/vector.h"
#include "player.h"
#include "room.h"
#include "room_object.h"
#include "world.h"
using namespace mbostock;
static const float axleRadius = .02f;
static const float axleLength = .14f;
static const float bodySize = .15f;
static const float eyeRadius = .02f;
static const float wheelRadius = .1f;
static const float wheelWeight = 1.f;
static const float counterWeightRadius = .07f;
static const float counterWeightOffset = .07f;
static const float counterWeight = .1f;
static const float forwardForce = 12.f;
static const float backwardForce = 5.f;
static const float turnForce = 1.f;
static const float motorFriction = 1.f;
static const float brakeFriction = 3.f;
static const Vector spokeColor(.5f, .5f, .5f);
static const Vector eyeColor(.5f, .5f, .5f);
static const Vector axleMaterialDiffuse(.9f, .8f, .8f);
static const Vector tireMaterialDiffuse(.1f, .1f, .1f);
static const Vector bodyMaterialDiffuse(.6f, .2f, .3f);
namespace mbostock {
/**
* A small model which displays just the player's wheel, so that this model
* can be compiled into a display list for faster display. This model uses the
* parent model's quadric.
*/
class PlayerWheelModel : public Model {
public:
PlayerWheelModel(const PlayerModel& model);
virtual void display();
private:
const PlayerModel& model_;
};
/**
* A small model which displays just the player's body (and axle, and eyes),
* so that this model can be compiled into a display list for faster display.
* This model uses the parent model's quadric.
*/
class PlayerBodyModel : public Model {
public:
PlayerBodyModel(const PlayerModel& model);
virtual void display();
private:
const PlayerModel& model_;
};
}
PlayerWheelModel::PlayerWheelModel(const PlayerModel& model)
: model_(model) {
}
void PlayerWheelModel::display() {
/* Spokes. */
glDisable(GL_LIGHTING);
glColorv(spokeColor);
glLineWidth(2.f);
gluQuadricDrawStyle(model_.quadric_, GLU_LINE);
gluDisk(model_.quadric_, 0, wheelRadius - wheelRadius / 8.f, 8, 1);
gluQuadricDrawStyle(model_.quadric_, GLU_FILL);
glEnable(GL_LIGHTING);
/* Tire. */
glMaterialv(GL_FRONT_AND_BACK, GL_DIFFUSE, tireMaterialDiffuse);
glutSolidTorus(wheelRadius / 4.f, 3 * wheelRadius / 4.f, 16, 32);
}
PlayerBodyModel::PlayerBodyModel(const PlayerModel& model)
: model_(model) {
}
void PlayerBodyModel::display() {
float l = axleLength + wheelRadius / 4.f + wheelRadius;
/* Axle endcap. */
glMaterialv(GL_FRONT_AND_BACK, GL_DIFFUSE, axleMaterialDiffuse);
glPushMatrix();
glTranslatef(0.f, 0.f, -l / 2.f);
glRotatef(180.f, 0.f, 1.f, 0.f);
gluDisk(model_.quadric_, 0.f, axleRadius, 16, 8);
glPopMatrix();
/* Axle. */
glPushMatrix();
glTranslatef(0.f, 0.f, -l / 2.f);
gluCylinder(model_.quadric_, axleRadius, axleRadius, l, 16, 1);
glPopMatrix();
/* Axle endcap. */
glPushMatrix();
glTranslatef(0.f, 0.f, l / 2.f);
gluDisk(model_.quadric_, 0.f, axleRadius, 16, 8);
glPopMatrix();
/* Body. */
glMaterialv(GL_FRONT_AND_BACK, GL_DIFFUSE, bodyMaterialDiffuse);
glutSolidCube(bodySize);
/* Eyes. */
static const float eyeDepth = .01f;
glMaterialv(GL_FRONT_AND_BACK, GL_DIFFUSE, axleMaterialDiffuse);
glColorv(eyeColor);
glTranslatef(bodySize / 2.f, bodySize / 6.f, bodySize / 4.f);
glPushMatrix();
glRotatef(90.f, 0.f, 1.f, 0.f);
gluCylinder(model_.quadric_, eyeRadius, eyeRadius, eyeDepth, 16, 1);
glTranslatef(0.f, 0.f, eyeDepth);
gluDisk(model_.quadric_, 0, eyeRadius, 8, 1);
glTranslatef(0.f, -.004f, .001f);
glMaterialv(GL_FRONT_AND_BACK, GL_DIFFUSE, tireMaterialDiffuse);
gluDisk(model_.quadric_, 0, eyeRadius / 2.f, 8, 1);
glMaterialv(GL_FRONT_AND_BACK, GL_DIFFUSE, axleMaterialDiffuse);
glPopMatrix();
glTranslatef(0.f, 0.f, -bodySize / 2.f);
glPushMatrix();
glRotatef(90.f, 0.f, 1.f, 0.f);
gluCylinder(model_.quadric_, eyeRadius, eyeRadius, eyeDepth, 16, 1);
glTranslatef(0.f, 0.f, eyeDepth);
gluDisk(model_.quadric_, 0, eyeRadius, 8, 1);
glTranslatef(0.f, -.004f, .001f);
glMaterialv(GL_FRONT_AND_BACK, GL_DIFFUSE, tireMaterialDiffuse);
gluDisk(model_.quadric_, 0, eyeRadius / 2.f, 8, 1);
glPopMatrix();
}
PlayerModel::PlayerModel(const Player& player)
: player_(player), quadric_(NULL),
wheelModel_(Models::compile(new PlayerWheelModel(*this))),
bodyModel_(Models::compile(new PlayerBodyModel(*this))) {
for (int i = 0; i < 15; i++) {
orientation_[i] = 0.f;
}
orientation_[15] = 1.f;
}
PlayerModel::~PlayerModel() {
delete wheelModel_;
delete bodyModel_;
if (quadric_ != NULL) {
gluDeleteQuadric(quadric_);
}
}
void PlayerModel::initialize() {
if (quadric_ != NULL) {
gluDeleteQuadric(quadric_);
}
quadric_ = gluNewQuadric();
wheelModel_->initialize();
bodyModel_->initialize();
}
float* PlayerModel::orientation() {
const Vector& x = player_.x();
const Vector& y = player_.y();
const Vector& z = player_.z();
orientation_[0] = x.x; orientation_[1] = x.y; orientation_[2] = x.z;
orientation_[4] = y.x; orientation_[5] = y.y; orientation_[6] = y.z;
orientation_[8] = z.x; orientation_[9] = z.y; orientation_[10] = z.z;
return orientation_;
}
void PlayerModel::display() {
Materials::blank().bind();
glPushMatrix();
glTranslatev(player_.origin());
glMultMatrixf(orientation());
/* Left wheel. */
if (!World::world()->debug() || player_.leftWheelFriction()) {
glPushMatrix();
glTranslatef(0.f, 0.f, -axleLength / 2.f - wheelRadius / 2.f);
glRotatef(player_.leftWheelAngle(), 0.f, 0.f, 1.f);
glRotatef(180.f, 0.f, 1.f, 0.f);
wheelModel_->display();
glPopMatrix();
}
/* Right wheel. */
if (!World::world()->debug() || player_.rightWheelFriction()) {
glPushMatrix();
glTranslatef(0.f, 0.f, axleLength / 2.f + wheelRadius / 2.f);
glRotatef(player_.rightWheelAngle(), 0.f, 0.f, 1.f);
wheelModel_->display();
glPopMatrix();
}
/* Axes. */
if (World::world()->debug()) {
displayAxes();
}
/* Body. */
bodyModel_->display();
glPopMatrix();
}
void PlayerModel::displayAxes() {
glDisable(GL_LIGHTING);
glBegin(GL_LINES);
glColor3f(1.f, 0.f, 0.f);
glVertex3f(0.f, 0.f, 0.f);
glVertex3f(2 * bodySize, 0.f, 0.f);
glColor3f(0.f, 1.f, 0.f);
glVertex3f(0.f, 0.f, 0.f);
glVertex3f(0.f, 2 * bodySize, 0.f);
glColor3f(0.f, 0.f, 1.f);
glVertex3f(0.f, 0.f, 0.f);
glVertex3f(0.f, 0.f, 2 * bodySize);
glEnd();
glEnable(GL_LIGHTING);
}
Player::Player()
: turnState_(NONE), moveState_(NONE),
sphere_(Vector::ZERO(), wheelRadius * 2.f), model_(*this) {
counterWeight_.inverseMass = 1.f / counterWeight;
}
float Player::mass() const {
return counterWeight + 3.f;
}
Player::Wheel::Wheel()
: angle(0.f), angleStep(0.f) {
inverseMass = 1.f / wheelWeight;
}
void Player::setOrigin(const Vector& origin) {
static const float a2 = axleLength / 2.f;
leftWheel_.position = origin + Vector(0.f, 0.f, -a2);
rightWheel_.position = origin + Vector(0.f, 0.f, a2);
body_.position = origin;
counterWeight_.position = origin + Vector(-counterWeightOffset, 0.f, 0.f);
leftWheel_.previousPosition = leftWheel_.position;
rightWheel_.previousPosition = rightWheel_.position;
body_.previousPosition = body_.position;
counterWeight_.previousPosition = counterWeight_.position;
origin_ = origin;
x_ = Vector::X();
y_ = Vector::Y();
z_ = Vector::Z();
}
void Player::setVelocity(const Vector& velocity) {
/* TODO: Rotate the player to face the velocity. But this works for now. */
if (velocity.x < 0) {
static const float a2 = axleLength / 2.f;
leftWheel_.position = origin_ + Vector(0.f, 0.f, a2);
rightWheel_.position = origin_ + Vector(0.f, 0.f, -a2);
counterWeight_.position = origin_ + Vector(counterWeightOffset, 0.f, 0.f);
leftWheel_.previousPosition = leftWheel_.position;
rightWheel_.previousPosition = rightWheel_.position;
counterWeight_.previousPosition = counterWeight_.position;
}
Vector v = velocity * ParticleSimulator::timeStep();
leftWheel_.previousPosition = leftWheel_.position - v;
rightWheel_.previousPosition = rightWheel_.position - v;
body_.previousPosition = body_.position - v;
counterWeight_.previousPosition = counterWeight_.position - v;
}
void Player::resetForces() {
leftWheel_.force = Vector::ZERO();
rightWheel_.force = Vector::ZERO();
body_.force = Vector::ZERO();
counterWeight_.force = Vector::ZERO();
/* Moving forces. */
switch (moveState_) {
case FORWARD: {
leftWheel_.applyForce(z_, forwardForce);
rightWheel_.applyForce(z_, forwardForce);
break;
}
case BACKWARD: {
leftWheel_.applyForce(z_, -backwardForce);
rightWheel_.applyForce(z_, -backwardForce);
break;
}
}
/* Turning forces. */
switch (turnState_) {
case RIGHT: {
leftWheel_.applyForce(z_, turnForce);
rightWheel_.applyForce(z_, -turnForce);
break;
}
case LEFT: {
leftWheel_.applyForce(z_, -turnForce);
rightWheel_.applyForce(z_, turnForce);
break;
}
}
/* Uniform drag forces. */
if ((turnState_ != NONE) || (moveState_ == FORWARD)) {
leftWheel_.applyQuadraticDrag(motorFriction);
rightWheel_.applyQuadraticDrag(motorFriction);
} else {
leftWheel_.applyLinearDrag(brakeFriction);
rightWheel_.applyLinearDrag(brakeFriction);
}
/* Flipping force if upside-down. */
if ((leftWheel_.contact && rightWheel_.contact)
&& (y_.y < (.5 * leftWheel_.contactNormal.y))) {
counterWeight_.force -= y_;
}
}
void Player::Wheel::applyContact(const RoomObject& o) {
const Vector& normal = Constraints::projection().normal;
if ((normal.y > o.slip()) && (!contact || (contactNormal.y < normal.y))) {
contactNormal = normal;
contactVelocity = o.velocity(position);
contact = true;
}
}
void Player::Wheel::applyForce(const Vector& z, float f) {
if (friction()) {
force += z.cross(contactNormal) * -f;
}
}
void Player::Wheel::applyLinearDrag(float kd) {
if (friction()) {
Vector v = (position - previousPosition - contactVelocity)
/ ParticleSimulator::timeStep();
float vs = v.squared();
if (vs < 1E-3) {
kd *= 10.f;
} else if (vs < 1E-2) {
kd *= 2.f;
}
force -= v * kd;
}
}
void Player::Wheel::applyQuadraticDrag(float kd) {
if (friction()) {
Vector v = (position - previousPosition - contactVelocity)
/ ParticleSimulator::timeStep();
force -= v * v.length() * kd;
}
}
void Player::applyForce(UnaryForce& force) {
force.apply(leftWheel_);
force.apply(rightWheel_);
force.apply(body_);
force.apply(counterWeight_);
}
void Player::step(const ParticleSimulator& s) {
leftWheel_.contact = false;
rightWheel_.contact = false;
s.step(leftWheel_);
s.step(rightWheel_);
s.step(body_);
s.step(counterWeight_);
sphere_.x() = body_.position;
}
bool Player::intersects(const Shape& s) {
return s.intersects(sphere_);
}
bool Player::constrainOutside(const RoomObject& o) {
const Shape& s = o.shape();
bool contact = false;
if (s.intersects(sphere_)) {
if (Constraints::outside(leftWheel_, s, wheelRadius)) {
constrainGlancing(o);
leftWheel_.applyContact(o);
contact = true;
}
if (Constraints::outside(rightWheel_, s, wheelRadius)) {
constrainGlancing(o);
rightWheel_.applyContact(o);
contact = true;
}
if (Constraints::outside(body_, s, wheelRadius)) {
sphere_.x() = body_.position;
contact = true;
}
if (Constraints::outside(counterWeight_, s, counterWeightRadius)) {
contact = true;
}
}
return contact;
}
void Player::constrainGlancing(const RoomObject& o) {
const Projection& j = Constraints::projection();
/*
* This constraint only applies if we are moving parallel to the wall, so if
* the body particle isn't moving, don't do anything.
*/
if ((body_.position - body_.previousPosition).squared() < 1E-5) {
return;
}
/* For glancing collisions, align parallel to the wall. */
float n = j.normal.dot(z_);
if (fabsf(n) > .8f) {
Plane p = Plane(j.x, j.normal);
Vector x = p.project((leftWheel_.position + rightWheel_.position) / 2.f).x;
Vector wp1 = x + j.normal * wheelRadius;
Vector wp2 = x + j.normal * (wheelRadius + axleLength);
Vector bp = x + j.normal * (wheelRadius + axleLength / 2.f);
const Vector& lp = (n > 0.f) ? wp1 : wp2;
const Vector& rp = (n > 0.f) ? wp2 : wp1;
/*
* Only allow this constraint to move the player a small amount, as we don't
* want this constraint to apply in weird cases like hitting a corner.
* Otherwise, this constraint might throw the player large distances.
*/
if (((bp - body_.position).squared()
+ (lp - leftWheel_.position).squared()
+ (rp - rightWheel_.position).squared()) > 1E-3) {
return;
}
body_.position = bp;
leftWheel_.position = lp;
rightWheel_.position = rp;
/*
* Include a small fudge factor to push the player away from the wall, so
* that the player can turn. Otherwise, the player seemingly gets locked to
* the wall surface!
*/
body_.position += j.normal * 1E-4;
leftWheel_.position += j.normal * 1E-4;
rightWheel_.position += j.normal * 1E-4;
body_.previousPosition += j.normal * 1E-4;
leftWheel_.previousPosition += j.normal * 1E-4;
rightWheel_.previousPosition += j.normal * 1E-4;
}
}
void Player::constrainBody() {
Constraints::distance(leftWheel_, rightWheel_, axleLength);
Constraints::distance(leftWheel_, body_, axleLength / 2.f);
Constraints::distance(rightWheel_, body_, axleLength / 2.f);
static const float d
= sqrtf(axleLength * axleLength / 4.f
+ counterWeightOffset * counterWeightOffset);
Constraints::distance(leftWheel_, counterWeight_, d);
Constraints::distance(rightWheel_, counterWeight_, d);
Constraints::distance(body_, counterWeight_, counterWeightOffset);
}
void Player::constrainInternal() {
leftWheel_.constrainDirection(z_);
rightWheel_.constrainDirection(z_);
constrainBody();
origin_ = body_.position;
z_ = (rightWheel_.position - leftWheel_.position) / axleLength;
x_ = (body_.position - counterWeight_.position) / counterWeightOffset;
y_ = -x_.cross(z_);
}
void Player::Wheel::constrainDirection(const Vector& z) {
if (!friction()) {
angle += angleStep;
return; // no normal force, no friction
}
/* Don't let upwards-moving platforms launch us into the air. */
Vector cv = contactVelocity;
if (cv.y > 0) {
cv.y = 0.f;
}
/* Cancel the sideways component of the velocity. */
Vector v = position - previousPosition - cv;
v -= z * z.dot(v);
previousPosition = position - v - cv;
/* Rotate the wheel. */
Vector forward = z.cross(contactNormal);
Vector vForward = forward * forward.dot(v);
float l = vForward.length();
if (vForward.cross(contactNormal).dot(z) > 0) {
l *= -1;
}
angleStep = 360.f * l / (M_PI * 2 * wheelRadius);
angle += angleStep;
}
void Player::jiggle() {
Vector v = Vector::randomVector(ParticleSimulator::timeStep());
v.y = ParticleSimulator::timeStep();
leftWheel_.position += v;
rightWheel_.position += v;
body_.position += v;
counterWeight_.position += v;
}
void Player::move(Direction d) {
switch (d) {
case LEFT: {
if (turnState_ == NONE) {
turnState_ = LEFT;
} else {
turnState_ = NONE;
}
break;
}
case RIGHT: {
if (turnState_ == NONE) {
turnState_ = RIGHT;
} else {
turnState_ = NONE;
}
break;
}
case FORWARD: {
if (moveState_ == NONE) {
moveState_ = FORWARD;
} else {
moveState_ = NONE;
}
break;
}
case BACKWARD: {
if (moveState_ == NONE) {
moveState_ = BACKWARD;
} else {
moveState_ = NONE;
}
break;
}
}
}
void Player::stop() {
turnState_ = NONE;
moveState_ = NONE;
}
void Player::stop(Direction d) {
switch (d) {
case LEFT: {
if (turnState_ != NONE) {
turnState_ = NONE;
} else {
turnState_ = RIGHT;
}
break;
}
case RIGHT: {
if (turnState_ != NONE) {
turnState_ = NONE;
} else {
turnState_ = LEFT;
}
break;
}
case FORWARD: {
if (moveState_ != NONE) {
moveState_ = NONE;
} else {
moveState_ = BACKWARD;
}
break;
}
case BACKWARD: {
if (moveState_ != NONE) {
moveState_ = NONE;
} else {
moveState_ = FORWARD;
}
break;
}
}
}