virtualx-engine/scene/3d/character_camera.cpp
Ferenc Arn bd7ba0b664 Use right handed coordinate system for rotation matrices and quaternions. Also fixes Euler angles (XYZ convention, which is used as default by Blender).
Furthermore, functions which expect a rotation matrix will now give an error simply, rather than trying to orthonormalize such matrices. The documentation for such functions has be updated accordingly.

This commit breaks code using 3D rotations, and is a part of the breaking changes in 2.1 -> 3.0 transition. The code affected within Godot code base is fixed in this commit.
2017-01-03 17:41:04 -06:00

718 lines
18 KiB
C++

/*************************************************************************/
/* character_camera.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* http://www.godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "character_camera.h"
#include "physics_body.h"
#if 0
void CharacterCamera::_set(const String& p_name, const Variant& p_value) {
if (p_name=="type")
set_camera_type((CameraType)((int)(p_value)));
else if (p_name=="orbit")
set_orbit(p_value);
else if (p_name=="height")
set_height(p_value);
else if (p_name=="inclination")
set_inclination(p_value);
else if (p_name=="max_orbit_x")
set_max_orbit_x(p_value);
else if (p_name=="min_orbit_x")
set_min_orbit_x(p_value);
else if (p_name=="max_distance")
set_max_distance(p_value);
else if (p_name=="min_distance")
set_min_distance(p_value);
else if (p_name=="distance")
set_distance(p_value);
else if (p_name=="clip")
set_clip(p_value);
else if (p_name=="autoturn")
set_autoturn(p_value);
else if (p_name=="autoturn_tolerance")
set_autoturn_tolerance(p_value);
else if (p_name=="autoturn_speed")
set_autoturn_speed(p_value);
}
Variant CharacterCamera::_get(const String& p_name) const {
if (p_name=="type")
return get_camera_type();
else if (p_name=="orbit")
return get_orbit();
else if (p_name=="height")
return get_height();
else if (p_name=="inclination")
return get_inclination();
else if (p_name=="max_orbit_x")
return get_max_orbit_x();
else if (p_name=="min_orbit_x")
return get_min_orbit_x();
else if (p_name=="max_distance")
return get_max_distance();
else if (p_name=="min_distance")
return get_min_distance();
else if (p_name=="distance")
return get_distance();
else if (p_name=="clip")
return has_clip();
else if (p_name=="autoturn")
return has_autoturn();
else if (p_name=="autoturn_tolerance")
return get_autoturn_tolerance();
else if (p_name=="autoturn_speed")
return get_autoturn_speed();
return Variant();
}
void CharacterCamera::_get_property_list( List<PropertyInfo> *p_list) const {
p_list->push_back( PropertyInfo( Variant::INT, "type", PROPERTY_HINT_ENUM, "Fixed,Follow") );
p_list->push_back( PropertyInfo( Variant::VECTOR2, "orbit" ) );
p_list->push_back( PropertyInfo( Variant::REAL, "height", PROPERTY_HINT_RANGE,"-1024,1024,0.01" ) );
p_list->push_back( PropertyInfo( Variant::REAL, "inclination", PROPERTY_HINT_RANGE,"-90,90,0.01" ) ); ;
p_list->push_back( PropertyInfo( Variant::REAL, "max_orbit_x", PROPERTY_HINT_RANGE,"-90,90,0.01" ) );
p_list->push_back( PropertyInfo( Variant::REAL, "min_orbit_x", PROPERTY_HINT_RANGE,"-90,90,0.01" ) );
p_list->push_back( PropertyInfo( Variant::REAL, "min_distance", PROPERTY_HINT_RANGE,"0,100,0.01" ) );
p_list->push_back( PropertyInfo( Variant::REAL, "max_distance", PROPERTY_HINT_RANGE,"0,100,0.01" ) );
p_list->push_back( PropertyInfo( Variant::REAL, "distance", PROPERTY_HINT_RANGE,"0.01,1024,0,01") );
p_list->push_back( PropertyInfo( Variant::BOOL, "clip") );
p_list->push_back( PropertyInfo( Variant::BOOL, "autoturn") );
p_list->push_back( PropertyInfo( Variant::REAL, "autoturn_tolerance", PROPERTY_HINT_RANGE,"1,90,0.01") );
p_list->push_back( PropertyInfo( Variant::REAL, "autoturn_speed", PROPERTY_HINT_RANGE,"1,90,0.01") );
}
void CharacterCamera::_compute_camera() {
// update the transform with the next proposed transform (camera is 1 logic frame delayed)
/*
float time = get_root_node()->get_frame_time();
Vector3 oldp = accepted.get_origin();
Vector3 newp = proposed.get_origin();
float frame_dist = time *
if (oldp.distance_to(newp) >
*/
float time = get_root_node()->get_frame_time();
if (true) {
if (clip_ray[0].clipped && clip_ray[1].clipped && clip_ray[2].clipped) {
//all have been clipped
proposed.origin=clip_ray[1].clip_pos;
} else {
Vector3 rel=proposed.origin-target_pos;
if (clip_ray[0].clipped && !clip_ray[2].clipped) {
float distance = target_pos.distance_to(clip_ray[0].clip_pos);
real_t amount = 1.0-(distance/clip_len);
amount = CLAMP(amount,0,1);
rel=Matrix3(Vector3(0,1,0)),
rotate_orbit(Vector2(0,autoturn_speed*time*amount));
}
if (clip_ray[2].clipped && !clip_ray[0].clipped) {
float distance = target_pos.distance_to(clip_ray[2].clip_pos);
real_t amount = 1.0-(distance/clip_len);
amount = CLAMP(amount,0,1);
rotate_orbit(Vector2(0,-autoturn_speed*time*amount));
}
}
}
Transform final;
static float pos_ratio = 0.9;
static float rot_ratio = 10;
Vector3 vec1 = accepted.origin;
Vector3 vec2 = proposed.origin;
final.origin = vec2.linear_interpolate(vec1, pos_ratio * time);;
Quat q1 = accepted.basis;
Quat q2 = proposed.basis;
final.basis = q1.slerp(q2, rot_ratio * time);
accepted=final;
_update_camera();
// calculate the next proposed transform
Vector3 new_pos;
Vector3 character_pos = get_global_transform().origin;
character_pos.y+=height; // height compensate
if(type==CAMERA_FOLLOW) {
/* calculate some variables */
Vector3 rel = follow_pos - character_pos;
float l = rel.length();
Vector3 rel_n = (l > 0) ? (rel/l) : Vector3();
#if 1
float ang = Math::acos(rel_n.dot( Vector3(0,1,0) ));
Vector3 tangent = rel_n;
tangent.y=0; // get rid of y
if (tangent.length_squared() < CMP_EPSILON2)
tangent=Vector3(0,0,1); // use Z as tangent if rel is parallel to y
else
tangent.normalize();
/* now start applying the rules */
//clip distance
if (l > max_distance)
l=max_distance;
if (l < min_distance)
l=min_distance;
//fix angle
float ang_min = Math_PI * 0.5 + Math::deg2rad(min_orbit_x);
float ang_max = Math_PI * 0.5 + Math::deg2rad(max_orbit_x);
if (ang<ang_min)
ang=ang_min;
if (ang>ang_max)
ang=ang_max;
/* finally, rebuild the validated camera position */
new_pos=Vector3(0,Math::cos(ang),0);
new_pos+=tangent*Math::sin(ang);
new_pos*=l;
new_pos+=character_pos;
#else
if (l > max_distance)
l=max_distance;
if (l < min_distance)
l=min_distance;
new_pos = character_pos + rel_n * l;
#endif
follow_pos=new_pos;
} else if (type==CAMERA_FIXED) {
if (distance<min_distance)
distance=min_distance;
if (distance>max_distance)
distance=max_distance;
if (orbit.x<min_orbit_x)
orbit.x=min_orbit_x;
if (orbit.x>max_orbit_x)
orbit.x=max_orbit_x;
Matrix3 m;
m.rotate(Vector3(0,1,0),-Math::deg2rad(orbit.y));
m.rotate(Vector3(1,0,0),-Math::deg2rad(orbit.x));
new_pos = (m.get_axis(2) * distance) + character_pos;
if (use_lookat_target) {
Transform t = get_global_transform();
Vector3 y = t.basis.get_axis(1).normalized();
Vector3 z = lookat_target - character_pos;
z= (z - y * y.dot(z)).normalized();
orbit.y = -Math::rad2deg(Math::atan2(z.x,z.z)) + 180;
/*
Transform t = get_global_transform();
Vector3 y = t.basis.get_axis(1).normalized();
Vector3 z = lookat_target - t.origin;
z= (z - y * y.dot(z)).normalized();
Vector3 x = z.cross(y).normalized();
Transform t2;
t2.basis.set_axis(0,x);
t2.basis.set_axis(1,y);
t2.basis.set_axis(2,z);
t2.origin=t.origin;
Vector3 local = t2.xform_inv(camera_pos);
float ang = Math::atan2(local.x,local.y);
*/
/*
Vector3 vec1 = lookat_target - new_pos;
vec1.normalize();
Vector3 vec2 = character_pos - new_pos;
vec2.normalize();
float dot = vec1.dot(vec2);
printf("dot %f\n", dot);
if ( dot < 0.5) {
rotate_orbit(Vector2(0, 90));
};
*/
};
}
Vector3 target;
if (use_lookat_target) {
target = lookat_target;
} else {
target = character_pos;
};
proposed.set_look_at(new_pos,target,Vector3(0,1,0));
proposed = proposed * Transform(Matrix3(Vector3(1,0,0),Math::deg2rad(inclination)),Vector3()); //inclination
Vector<RID> exclude;
exclude.push_back(target_body);
Vector3 rel = new_pos-target;
for(int i=0;i<3;i++) {
PhysicsServer::get_singleton()->query_intersection(clip_ray[i].query,get_world().get_space(),exclude);
PhysicsServer::get_singleton()->query_intersection_segment(clip_ray[i].query,target,target+Matrix3(Vector3(0,1,0),Math::deg2rad(autoturn_tolerance*(i-1.0))).xform(rel));
clip_ray[i].clipped=false;
clip_ray[i].clip_pos=Vector3();
}
target_pos=target;
clip_len=rel.length();
}
void CharacterCamera::set_use_lookat_target(bool p_use, const Vector3 &p_lookat) {
use_lookat_target = p_use;
lookat_target = p_lookat;
};
void CharacterCamera::_notification(int p_what) {
switch(p_what) {
case NOTIFICATION_PROCESS: {
_compute_camera();
} break;
case NOTIFICATION_ENTER_SCENE: {
if (type==CAMERA_FOLLOW) {
set_orbit(orbit);
set_distance(distance);
}
accepted=get_global_transform();
proposed=accepted;
target_body = RID();
Node* parent = get_parent();
while (parent) {
PhysicsBody* p = parent->cast_to<PhysicsBody>();
if (p) {
target_body = p->get_body();
break;
};
parent = parent->get_parent();
};
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
} break;
case NOTIFICATION_EXIT_SCENE: {
if (type==CAMERA_FOLLOW) {
distance=get_distance();
orbit=get_orbit();
}
} break;
case NOTIFICATION_BECAME_CURRENT: {
set_process(true);
} break;
case NOTIFICATION_LOST_CURRENT: {
set_process(false);
} break;
}
}
void CharacterCamera::set_camera_type(CameraType p_camera_type) {
if (p_camera_type==type)
return;
type=p_camera_type;
// do conversions
}
CharacterCamera::CameraType CharacterCamera::get_camera_type() const {
return type;
}
void CharacterCamera::set_orbit(const Vector2& p_orbit) {
orbit=p_orbit;
if(type == CAMERA_FOLLOW && is_inside_scene()) {
Vector3 char_pos = get_global_transform().origin;
char_pos.y+=height;
float d = char_pos.distance_to(follow_pos);
Matrix3 m;
m.rotate(Vector3(0,1,0),-orbit.y);
m.rotate(Vector3(1,0,0),-orbit.x);
follow_pos=char_pos + m.get_axis(2) * d;
}
}
void CharacterCamera::set_orbit_x(float p_x) {
orbit.x=p_x;
if(type == CAMERA_FOLLOW && is_inside_scene())
set_orbit(Vector2( p_x, get_orbit().y ));
}
void CharacterCamera::set_orbit_y(float p_y) {
orbit.y=p_y;
if(type == CAMERA_FOLLOW && is_inside_scene())
set_orbit(Vector2( get_orbit().x, p_y ));
}
Vector2 CharacterCamera::get_orbit() const {
if (type == CAMERA_FOLLOW && is_inside_scene()) {
Vector3 char_pos = get_global_transform().origin;
char_pos.y+=height;
Vector3 rel = (follow_pos - char_pos).normalized();
Vector2 ret_orbit;
ret_orbit.x = Math::acos( Vector3(0,1,0).dot( rel ) ) - Math_PI * 0.5;
ret_orbit.y = Math::atan2(rel.x,rel.z);
return ret_orbit;
}
return orbit;
}
void CharacterCamera::rotate_orbit(const Vector2& p_relative) {
if (type == CAMERA_FOLLOW && is_inside_scene()) {
Matrix3 m;
m.rotate(Vector3(0,1,0),-Math::deg2rad(p_relative.y));
m.rotate(Vector3(1,0,0),-Math::deg2rad(p_relative.x));
Vector3 char_pos = get_global_transform().origin;
char_pos.y+=height;
Vector3 rel = (follow_pos - char_pos);
rel = m.xform(rel);
follow_pos=char_pos+rel;
}
orbit+=p_relative;
}
void CharacterCamera::set_height(float p_height) {
height=p_height;
}
float CharacterCamera::get_height() const {
return height;
}
void CharacterCamera::set_max_orbit_x(float p_max) {
max_orbit_x=p_max;
}
float CharacterCamera::get_max_orbit_x() const {
return max_orbit_x;
}
void CharacterCamera::set_min_orbit_x(float p_min) {
min_orbit_x=p_min;
}
float CharacterCamera::get_min_orbit_x() const {
return min_orbit_x;
}
float CharacterCamera::get_min_distance() const {
return min_distance;
}
float CharacterCamera::get_max_distance() const {
return max_distance;
}
void CharacterCamera::set_min_distance(float p_min) {
min_distance=p_min;
}
void CharacterCamera::set_max_distance(float p_max) {
max_distance = p_max;
}
void CharacterCamera::set_distance(float p_distance) {
if (type == CAMERA_FOLLOW && is_inside_scene()) {
Vector3 char_pos = get_global_transform().origin;
char_pos.y+=height;
Vector3 rel = (follow_pos - char_pos).normalized();
rel*=p_distance;
follow_pos=char_pos+rel;
}
distance=p_distance;
}
float CharacterCamera::get_distance() const {
if (type == CAMERA_FOLLOW && is_inside_scene()) {
Vector3 char_pos = get_global_transform().origin;
char_pos.y+=height;
return (follow_pos - char_pos).length();
}
return distance;
}
void CharacterCamera::set_clip(bool p_enabled) {
clip=p_enabled;
}
bool CharacterCamera::has_clip() const {
return clip;
}
void CharacterCamera::set_autoturn(bool p_enabled) {
autoturn=p_enabled;
}
bool CharacterCamera::has_autoturn() const {
return autoturn;
}
void CharacterCamera::set_autoturn_tolerance(float p_degrees) {
autoturn_tolerance=p_degrees;
}
float CharacterCamera::get_autoturn_tolerance() const {
return autoturn_tolerance;
}
void CharacterCamera::set_inclination(float p_degrees) {
inclination=p_degrees;
}
float CharacterCamera::get_inclination() const {
return inclination;
}
void CharacterCamera::set_autoturn_speed(float p_speed) {
autoturn_speed=p_speed;
}
float CharacterCamera::get_autoturn_speed() const {
return autoturn_speed;
}
void CharacterCamera::_bind_methods() {
ClassDB::bind_method(_MD("set_camera_type","type"),&CharacterCamera::set_camera_type);
ClassDB::bind_method(_MD("get_camera_type"),&CharacterCamera::get_camera_type);
ClassDB::bind_method(_MD("set_orbit","orbit"),&CharacterCamera::set_orbit);
ClassDB::bind_method(_MD("get_orbit"),&CharacterCamera::get_orbit);
ClassDB::bind_method(_MD("set_orbit_x","x"),&CharacterCamera::set_orbit_x);
ClassDB::bind_method(_MD("set_orbit_y","y"),&CharacterCamera::set_orbit_y);
ClassDB::bind_method(_MD("set_min_orbit_x","x"),&CharacterCamera::set_min_orbit_x);
ClassDB::bind_method(_MD("get_min_orbit_x"),&CharacterCamera::get_min_orbit_x);
ClassDB::bind_method(_MD("set_max_orbit_x","x"),&CharacterCamera::set_max_orbit_x);
ClassDB::bind_method(_MD("get_max_orbit_x"),&CharacterCamera::get_max_orbit_x);
ClassDB::bind_method(_MD("rotate_orbit"),&CharacterCamera::rotate_orbit);
ClassDB::bind_method(_MD("set_distance","distance"),&CharacterCamera::set_distance);
ClassDB::bind_method(_MD("get_distance"),&CharacterCamera::get_distance);
ClassDB::bind_method(_MD("set_clip","enable"),&CharacterCamera::set_clip);
ClassDB::bind_method(_MD("has_clip"),&CharacterCamera::has_clip);
ClassDB::bind_method(_MD("set_autoturn","enable"),&CharacterCamera::set_autoturn);
ClassDB::bind_method(_MD("has_autoturn"),&CharacterCamera::has_autoturn);
ClassDB::bind_method(_MD("set_autoturn_tolerance","degrees"),&CharacterCamera::set_autoturn_tolerance);
ClassDB::bind_method(_MD("get_autoturn_tolerance"),&CharacterCamera::get_autoturn_tolerance);
ClassDB::bind_method(_MD("set_autoturn_speed","speed"),&CharacterCamera::set_autoturn_speed);
ClassDB::bind_method(_MD("get_autoturn_speed"),&CharacterCamera::get_autoturn_speed);
ClassDB::bind_method(_MD("set_use_lookat_target","use","lookat"),&CharacterCamera::set_use_lookat_target, DEFVAL(Vector3()));
ClassDB::bind_method(_MD("_ray_collision"),&CharacterCamera::_ray_collision);
BIND_CONSTANT( CAMERA_FIXED );
BIND_CONSTANT( CAMERA_FOLLOW );
}
void CharacterCamera::_ray_collision(Vector3 p_point, Vector3 p_normal, int p_subindex, ObjectID p_against,int p_idx) {
clip_ray[p_idx].clip_pos=p_point;
clip_ray[p_idx].clipped=true;
};
Transform CharacterCamera::get_camera_transform() const {
return accepted;
}
CharacterCamera::CharacterCamera() {
type=CAMERA_FOLLOW;
height=1;
orbit=Vector2(0,0);
distance=3;
min_distance=2;
max_distance=5;
autoturn=false;
autoturn_tolerance=15;
autoturn_speed=20;
min_orbit_x=-50;
max_orbit_x=70;
inclination=0;
clip=false;
use_lookat_target = false;
for(int i=0;i<3;i++) {
clip_ray[i].query=PhysicsServer::get_singleton()->query_create(this, "_ray_collision", i, true);
clip_ray[i].clipped=false;
}
}
CharacterCamera::~CharacterCamera() {
for(int i=0;i<3;i++) {
PhysicsServer::get_singleton()->free(clip_ray[i].query);
}
}
#endif