scummvm/engines/icb/actor_view_pc.cpp
2020-10-06 09:51:20 +02:00

693 lines
18 KiB
C++

/* ResidualVM - A 3D game interpreter
*
* ResidualVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the AUTHORS
* file distributed with this source distribution.
*
* Additional copyright for this file:
* Copyright (C) 1999-2000 Revolution Software Ltd.
* This code is based on source code created by Revolution Software,
* used with permission.
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "engines/icb/common/px_rccommon.h"
#include "engines/icb/common/px_common.h"
#include "engines/icb/p4.h"
#include "engines/icb/p4_generic.h"
#include "engines/icb/debug.h"
#include "engines/icb/mission.h"
#include "engines/icb/direct_input.h"
#include "engines/icb/global_objects.h"
#include "engines/icb/global_objects_psx.h"
#include "engines/icb/p4.h"
#include "engines/icb/actor.h"
#include "engines/icb/actor_pc.h"
#include "engines/icb/drawpoly_pc.h"
#include "engines/icb/common/px_capri_maths.h"
#include "engines/icb/gfx/gfxstub.h"
#include "engines/icb/gfx/gfxstub_dutch.h"
#include "engines/icb/gfx/gfxstub_rev_dutch.h"
#include "engines/icb/actor_view_pc.h"
#include "engines/icb/res_man.h"
#include "common/keyboard.h"
namespace ICB {
// Cute little puppies
extern char *pRGB;
extern char *pZa;
extern int RGBWidth;
extern int RGBHeight;
extern int RGBPitch;
extern int RGBBytesPerPixel;
RevRenderDevice renderingDevice;
extern int32 texturesUsedThisCycle;
// This controls autoanimation (0 = None, 1 = Backward, 2 = Forward)
uint32 auto_anim = 2;
// Camera and animation structures
psxCamera camera;
PXanim *pxanim;
SVECTOR rot; // Actor rotation
SVECTOR _crot; // Camera rotation
int uvframe = 0;
// Actor structure
psxActor av_actor;
// Global filename stuff
char cluster_name[32];
uint32 cluster_name_hash;
char raj_name[128];
uint32 raj_name_hash;
// Pointers to useful strings for the current actor
char *character_name;
char *anim_name;
const char *weapon_name;
char *outfit_name;
int framenum;
int32 g_repeats;
// Do we allow keyboard input to affect the actor viewing
bool8 g_av_userControlled = FALSE8;
// Lighting structure and coordinates, colour components
PSXLamp av_Light;
int16 av_LightX;
int16 av_LightY;
int16 av_LightZ;
int32 av_LightR;
int32 av_LightG;
int32 av_LightB;
int32 av_LightA;
bool8 av_LightDir;
bool8 av_autoR;
bool8 av_autoG;
bool8 av_autoB;
// Render coordinates
int16 av_x, av_y, av_z;
// Prototypes for functions used in this module
TextureHandle *GetRegisteredTexture(const char *, uint32, const char *, uint32, const char *, uint32);
void DrawFrame(const int frame);
void MakeCameraView();
void ResetCamera();
void ResetActor();
void InitLight();
void SetLight(int32 falloff);
void AutoCycleLight();
#define LIGHT_DISTANCE_FROM_ACTOR 100
#define LIGHT_HEIGHT_LIMIT 200
void InitActorView(const char *name, const char *outfit, const char *weapon, const char *anim, int16 ix, int16 iy, int16 iz) {
// Store initial offset coordinates
av_x = ix;
av_y = iy;
av_z = iz;
// Make hash filename of the character
char h_character[8];
HashFile(name, h_character);
// Make hash filename of the outfit
char h_outfit[8];
HashFile(outfit, h_outfit);
// Make the cluster name
sprintf(cluster_name, "\\C\\%s\\%s.OFT", h_character, h_outfit);
// Hash value for this cluster name
cluster_name_hash = NULL_HASH;
ResetCamera();
ResetActor();
raj_name_hash = NULL_HASH;
sprintf(raj_name, "%s\\%s.raj", weapon, anim);
anim_name = const_cast<char *>(anim);
weapon_name = const_cast<char *>(weapon);
outfit_name = const_cast<char *>(outfit);
character_name = const_cast<char *>(name);
// Animate the shape from frame to frame, looping
framenum = 0;
g_repeats = 0;
auto_anim = 2;
// Initialise a light to use
InitLight();
// Start the psx poly drawing ganky bit
InitDrawing();
// Load and select the appropriate texture
char texture_name[128];
uint32 texture_name_hash = NULL_HASH;
sprintf(texture_name, "material.revtex");
TextureHandle *texHan = GetRegisteredTexture(texture_name, texture_name_hash, texture_name, texture_name_hash, cluster_name, cluster_name_hash);
ChooseTexture(texHan);
}
void ChangeAnimPlaying(const char *pose, const char *anim, bool8 forwards, int32 repeats, int16 ix, int16 iy, int16 iz) {
// Set pose
if (pose) {
// New pose
weapon_name = const_cast<char *>(pose);
} else {
// Default is unarmed
weapon_name = "unarmed";
}
// Require anim parameter
if (anim == NULL)
Fatal_error("ChangeAnimPlaying() cannot set active animation to NULL!");
// Remake raj filename
raj_name_hash = NULL_HASH;
sprintf(raj_name, "%s\\%s.raj", weapon_name, anim);
// Change animation to use
anim_name = const_cast<char *>(anim);
// Set direction to run the anim
if (forwards)
auto_anim = 2;
else
auto_anim = 1;
pxanim = (PXanim *)rs_anims->Res_open(raj_name, raj_name_hash, cluster_name, cluster_name_hash);
// Set to the starting frame of this anim
if (forwards)
framenum = 0;
else
framenum = pxanim->frame_qty - 2;
g_repeats = repeats;
// Store initial offset coordinates
av_x = ix;
av_y = iy;
av_z = iz;
ResetCamera();
}
int ActorViewDraw() {
// Return value
int returnStatus = MID_ANIMATION;
// Alters the light nicely
AutoCycleLight();
// Call this each cycle to update our light
SetLight(500);
// This does most of the work
DrawFrame(framenum);
// This code acts upon user input to alter the actor's rotation and anim frame
// from the initialised defaults.
if (g_av_userControlled) {
// Increment in degrees
int dang = 5;
// Actor rotation
if (Read_DI_keys(Common::KEYCODE_LEFT)) {
rot.vy = (short)(rot.vy + 4096 * dang / 360);
}
if (Read_DI_keys(Common::KEYCODE_DOWN)) {
rot.vy = (short)(rot.vy - 4096 * dang / 360);
}
if (Read_DI_keys(Common::KEYCODE_UP)) {
rot.vx = (short)(rot.vx + 4096 * dang / 360);
}
if (Read_DI_keys(Common::KEYCODE_DOWN)) {
rot.vx = (short)(rot.vx - 4096 * dang / 360);
}
if (Read_DI_keys(Common::KEYCODE_PAGEUP)) {
rot.vz = (short)(rot.vz + 4096 * dang / 360);
}
if (Read_DI_keys(Common::KEYCODE_PAGEDOWN)) {
rot.vz = (short)(rot.vz - 4096 * dang / 360);
}
if (rot.vx > 4096)
rot.vx -= 4096;
if (rot.vy > 4096)
rot.vy -= 4096;
if (rot.vz > 4096)
rot.vz -= 4096;
if (rot.vx < -4096)
rot.vx += 4096;
if (rot.vy < -4096)
rot.vy += 4096;
if (rot.vz < -4096)
rot.vz += 4096;
// Set animation playing forwards
if (Read_DI_keys(Common::KEYCODE_1)) {
auto_anim = 2;
}
// Set animation playing backwards
if (Read_DI_keys(Common::KEYCODE_2)) {
auto_anim = 1;
}
// Previous frame
if (Read_DI_once_keys(Common::KEYCODE_MINUS)) {
auto_anim = 0;
framenum--;
}
// Next frame
if (Read_DI_once_keys(Common::KEYCODE_EQUALS)) {
auto_anim = 0;
framenum++;
}
}
// Auto animating backward
if (auto_anim == 1) {
framenum--;
}
// Auto forwards
else if (auto_anim) {
framenum++;
}
// Catch the loop for doing -1 in unsigned decimal
if (framenum > pxanim->frame_qty)
framenum = pxanim->frame_qty - 2;
if (framenum < 0) {
if (g_repeats > 0) {
g_repeats--;
} else {
if (auto_anim == 1)
returnStatus = ANIMATION_END;
}
framenum = pxanim->frame_qty - 2;
}
// Catch the loop for going past end of the animation
if (framenum > (pxanim->frame_qty - 2)) {
if (g_repeats > 0) {
g_repeats--;
} else {
if (auto_anim == 2)
returnStatus = ANIMATION_END;
}
framenum = 0;
}
// Catch illegal animations
if (framenum < 0)
framenum = 0;
// Draw the display list
drawOTList();
// Now copy the sucker to the screen
uint32 *address = (uint32 *)surface_manager->Lock_surface(working_buffer_id);
uint32 pitch = surface_manager->Get_pitch(working_buffer_id);
uint32 *source = (uint32 *)pRGB;
uint16 *zActor = (uint16 *)pZa;
uint32 *safe_ad = address;
for (int y = SCREEN_DEPTH; y; y--) {
uint32 *ad = safe_ad;
for (int x = SCREEN_WIDTH; x; x--) {
// If the z-map for this pixel is FFFF then this pixel doesn't contain actor
if (*zActor != 0xFFFF) {
*ad = *source;
*zActor = 0xFFFF;
}
++zActor;
++ad;
++source;
}
safe_ad += pitch / 4;
}
// Unlock the surface
surface_manager->Unlock_surface(working_buffer_id);
return returnStatus;
}
void DrawFrame(const int frame) {
// These structures are needed for the drawing code to accept our light
PSXLampList the_lights;
PSXShadeList the_shades;
the_lights.n = 1;
the_lights.states[0] = 0;
the_lights.lamps[0] = (PSXLamp *)(&av_Light);
the_shades.n = 0;
// Open the animation file
char bone_name[128];
char pose_name[128];
char mesh_name[128];
char smesh_name[128];
PSXrgb ambient;
ambient.r = 128;
ambient.g = 128;
ambient.b = 128;
pxanim = (PXanim *)rs_anims->Res_open(raj_name, raj_name_hash, cluster_name, cluster_name_hash);
PXFrameEnOfAnim(framenum, pxanim)->markers[ORG_POS];
// Make the actors orientation matrix
av_actor.rot = rot;
av_actor.rot.vy = (short)(av_actor.rot.vy);
// Make the root local-world matrix
RotMatrix_gte(&av_actor.rot, &av_actor.lw);
// Need to use marker to get correct actor height (making crouch look correct)
PXframe *frm = PXFrameEnOfAnim(framenum, pxanim);
PXmarker &marker = frm->markers[ORG_POS];
float mposx, mposy, mposz;
marker.GetXYZ(&mposx, &mposy, &mposz);
int dy = (int)mposy;
av_actor.lw.t[0] = 0;
av_actor.lw.t[1] = dy - 112;
av_actor.lw.t[2] = 0;
// Set the true rotation & position values from the ORG marker
av_actor.truePos.x = 0;
av_actor.truePos.y = dy - 112;
av_actor.truePos.z = 0;
av_actor.trueRot = av_actor.rot;
sprintf(pose_name, "%s\\pose.rap", weapon_name);
sprintf(bone_name, "%s\\%s.rab", weapon_name, anim_name);
sprintf(mesh_name, "mesh.rap");
sprintf(smesh_name, "mesh_shadow.rap");
uint32 mesh_hash = HashString(mesh_name);
rap_API *mesh = (rap_API *)rs_anims->Res_open(mesh_name, mesh_hash, cluster_name, cluster_name_hash);
uint32 smesh_hash = HashString(smesh_name);
rap_API *smesh = (rap_API *)rs_anims->Res_open(smesh_name, smesh_hash, cluster_name, cluster_name_hash);
uint32 pose_hash = HashString(pose_name);
rap_API *pose = (rap_API *)rs_anims->Res_open(pose_name, pose_hash, cluster_name, cluster_name_hash);
uint32 bone_hash = HashString(bone_name);
rab_API *rab = (rab_API *)rs_anims->Res_open(bone_name, bone_hash, cluster_name, cluster_name_hash);
ConvertRAP(pose);
ConvertRAP(smesh);
ConvertRAP(mesh);
// Some error checking
if (*(int *)mesh->id != *(int *)const_cast<char *>(RAP_API_ID)) {
Fatal_error("Wrong rap id value file %d api %d file:%s", mesh->id, RAP_API_ID, mesh_name);
}
if (mesh->schema != RAP_API_SCHEMA) {
Fatal_error("Wrong rap schema value file %d rap_api %d file:%s", mesh->schema, RAP_API_SCHEMA, mesh_name);
}
if (*(int *)pose->id != *(int *)const_cast<char *>(RAP_API_ID)) {
Fatal_error("Wrong rap id value file %d api %d file:%s", pose->id, RAP_API_ID, pose_name);
}
if (pose->schema != RAP_API_SCHEMA) {
Fatal_error("Wrong rap schema value file %d rap_api %d file:%s", pose->schema, RAP_API_SCHEMA, pose_name);
}
if (*(int *)rab->id != *(int *)const_cast<char *>(RAB_API_ID)) {
Fatal_error("Wrong rab id value file %d rab_api %d file:%s", rab->id, RAB_API_ID, bone_name);
}
if (rab->schema != RAB_API_SCHEMA) {
Fatal_error("Wrong rab schema value file %d rab_api %d file:%s", rab->schema, RAB_API_SCHEMA, bone_name);
}
if (mesh->nBones != rab->nBones) {
Fatal_error("mesh nBones != animation nBones %d != %d", mesh->nBones, rab->nBones);
}
// Pass in the linkage file and the bones file
Bone_Frame *bone_frame = rab->GetFrame(frame);
int brightness;
int debug = 1;
BoneDeformation *myBones[MAX_DEFORMABLE_BONES];
for (int i = 0; i < MAX_DEFORMABLE_BONES; i++) {
myBones[i] = NULL;
}
// Shadow stuff to play with
int nShadows = 0;
SVECTORPC p_n[3];
int p_d[3];
p_n[0].vx = 0;
p_n[0].vy = -1;
p_n[0].vz = 0;
p_d[0] = -118;
MATRIXPC local2screen; // not really bothered about this...
// Drawing finally
DrawActor4PC(&av_actor, &camera, bone_frame, mesh, pose, smesh, &ambient, &the_lights, &the_shades, nShadows, p_n, p_d, debug, uvframe, myBones, &brightness,
&local2screen);
uvframe++;
}
void MakeCameraView() {
RotMatrix_gte(&_crot, &camera.view);
// Include the x,y,z scalings
camera.view.m[0][0] = (short)(camera.view.m[0][0] * 1);
camera.view.m[0][1] = (short)(camera.view.m[0][1] * 1);
camera.view.m[0][2] = (short)(camera.view.m[0][2] * 1);
camera.view.m[1][0] = (short)(camera.view.m[1][0] * 1);
camera.view.m[1][1] = (short)(camera.view.m[1][1] * 1);
camera.view.m[1][2] = (short)(camera.view.m[1][2] * 1);
camera.view.m[2][0] = (short)(camera.view.m[2][0] * 4);
camera.view.m[2][1] = (short)(camera.view.m[2][1] * 4);
camera.view.m[2][2] = (short)(camera.view.m[2][2] * 4);
}
void ResetCamera() {
_crot.vx = (4096 * 180) / 360;
_crot.vy = (4096 * -30) / 360;
_crot.vz = 0;
camera.view.t[0] = 170 + av_x;
camera.view.t[1] = 0 + av_y;
camera.view.t[2] = 1800 + av_z;
camera.focLen = 619 * 4;
MakeCameraView();
}
void ResetActor() {
// Set up av_actor rotation
rot.vx = 0;
rot.vy = 0;
rot.vz = 0;
}
void InitLight() {
av_Light.nStates = 1; // One state
av_Light.w = 0; // Zero width
av_Light.b = 0; // Zero bounce
av_Light.anu = 0; // Don't use it
av_Light.type = OMNI_LIGHT; // OMNI
av_Light.ba = 0; // Means nothing for an OMNI
av_Light.bs = 0; // Means nothing for an OMNI
// Don't think these things are used...
av_Light.states[0].ans2 = 0;
av_Light.states[0].ane2 = (100 * 1) * (100 * 1);
// No shade...
av_Light.states[0].m = 128;
// Direction doesn't matter; it's an OMNI light
av_Light.states[0].vx = 4096; // Ignored for an OMNI light
av_Light.states[0].vy = 0; // Ignored for an OMNI light
av_Light.states[0].vz = 0; // Ignored for an OMNI light
// Initial angle
av_LightA = 0;
av_LightDir = TRUE8;
// Initial position
av_LightX = 0;
av_LightY = 0;
av_LightZ = LIGHT_DISTANCE_FROM_ACTOR;
// Initial colour (RED)
av_LightR = 255;
av_LightG = 0;
av_LightB = 0;
// Auto flags
av_autoR = FALSE8;
av_autoG = FALSE8;
av_autoB = FALSE8;
}
void AutoCycleLight() {
// Increase angle by 10 degrees
av_LightA += 10;
if (av_LightA >= 360)
av_LightA = 0;
// Convert to radians
double radians = (av_LightA * M_PI) / 180.0f;
// Now calculate z and x coordinates from this angle
av_LightX = (short)(sin(radians) * LIGHT_DISTANCE_FROM_ACTOR);
av_LightZ = (short)(cos(radians) * LIGHT_DISTANCE_FROM_ACTOR);
// Now bouce the light height between two fixed limits
if (av_LightDir) {
av_LightY = (short)(av_LightY + 10);
if (av_LightY > LIGHT_HEIGHT_LIMIT) {
av_LightY = LIGHT_HEIGHT_LIMIT;
av_LightDir = FALSE8;
}
} else {
av_LightY = (short)(av_LightY - 10);
if (av_LightY < -LIGHT_HEIGHT_LIMIT) {
av_LightY = -LIGHT_HEIGHT_LIMIT;
av_LightDir = TRUE8;
}
}
// Now fuck with the colours a bit
// Red component
if (av_autoR) {
av_LightR += 3;
if (av_LightR > 255) {
av_LightR = 255;
av_autoR = FALSE8;
}
} else {
av_LightR -= 2;
if (av_LightR < 0) {
av_LightR = 0;
av_autoR = TRUE8;
}
}
// Green component
if (av_autoG) {
av_LightG += 2;
if (av_LightG > 255) {
av_LightG = 255;
av_autoG = FALSE8;
}
} else {
av_LightG -= 3;
if (av_LightG < 0) {
av_LightG = 0;
av_autoG = TRUE8;
}
}
// Blue component
if (av_autoB) {
av_LightB += 7;
if (av_LightB > 255) {
av_LightB = 255;
av_autoB = FALSE8;
}
} else {
av_LightB -= 5;
if (av_LightB < 0) {
av_LightB = 0;
av_autoB = TRUE8;
}
}
}
void SetLight(int32 falloff) {
// Check colours are 0-255
if ((av_LightR > 255) || (av_LightR < 0) || (av_LightG > 255) || (av_LightG < 0) || (av_LightB > 255) || (av_LightB < 0))
Fatal_error("ActorView light rgb %d,%d,%d out of range (0-255)", av_LightR, av_LightG, av_LightB);
// Set colours (scale 0-255 to 0-4095)
av_Light.states[0].c.r = (short)((av_LightR * 4096) / 256);
av_Light.states[0].c.g = (short)((av_LightG * 4096) / 256);
av_Light.states[0].c.b = (short)((av_LightB * 4096) / 256);
// Set the v field of colour to be the maximum of r,g,b
av_Light.states[0].c.v = av_Light.states[0].c.r; // Start at red
if (av_Light.states[0].c.g > av_Light.states[0].c.v) // If green bigger
av_Light.states[0].c.v = av_Light.states[0].c.g; // Set to green
if (av_Light.states[0].c.b > av_Light.states[0].c.v) // If blue bigger
av_Light.states[0].c.v = av_Light.states[0].c.b; // Set to blue
av_Light.states[0].pos.vx = (int32)av_LightX;
av_Light.states[0].pos.vy = (int32)av_LightY;
av_Light.states[0].pos.vz = (int32)av_LightZ;
// And add the players position
av_Light.states[0].pos.vx += (int32)av_actor.truePos.x;
av_Light.states[0].pos.vy += (int32)av_actor.truePos.y;
av_Light.states[0].pos.vz += (int32)av_actor.truePos.z;
// Falloff
if (falloff == 0) {
av_Light.afu = 0; // Don't use it
} else {
av_Light.states[0].afs2 = (falloff * falloff) / 100; // (d/10)^2 = (d*d)/100
av_Light.states[0].afe2 = falloff * falloff; // d^2 = (d*d)
av_Light.afu = 1; // Use it
}
}
int my_sprintf(char *buf, const char *format...) {
char lbuf[256];
// Process the variable arguments
va_list arglist;
va_start(arglist, format);
int slen = vsnprintf(lbuf, 256, const_cast<char *>(format), arglist);
strncpy(buf, lbuf, slen);
buf[slen] = '\0';
return slen;
}
} // End of namespace ICB