After creating my Catmull Rom Spline such as:
vector3 SplineVector = newSpline.createCatmulRomSpline(vectorOne, vectorTwo, vectorThree, vectorFour, i);
However, when I read out the result from
vector3 SplineVector
I get garbage values.
Here is a listing of my spline class, a segment of my vector3 class and in the implementation within the initialization.
initialization:
for(float i = 0.0f; i <= 1.0f; i += 0.1f)
{
vector3 SplineVector = newSpline.createCatmulRomSpline(vectorOne, vectorTwo, vectorThree, vectorFour, i);
cout << "\n\ncurve pos X: " << SplineVector.getx();
cout << "\ncurve pos Y: " << SplineVector.gety();
cout << "\ncurve pos Z: " << SplineVector.getz();
}
Vector3:
class vector3
{
public:
vector3::vector3():x(0),y(0),z(0)
{
}
vector3::~vector3()
{
}
vector3(float);
vector3(float inx,float iny,float inz): x(inx), y(iny), z(inz)
{
}
//Vector operators
_inline vector3 operator=(const vector3& invec){ // Assignment
return vector3(this->x=invec.x,this->y=invec.y,this->z=invec.z);
}
_inline vector3 operator+(const vector3& invec){//Addition
return vector3(this->x+invec.x,this->y+invec.y,this->z+invec.z);
}
_inline vector3 operator-(const vector3& invec){//Subtraction
return vector3(this->x-invec.x,this->y-invec.y,this->z-invec.z);
}
_inline vector3 operator*(const vector3& invec){//Multiplication
return vector3(this->x*invec.x,this->y*invec.y,this->z*invec.z);
}
_inline vector3 operator/(const vector3& invec){//Division
return vector3(this->x/invec.x,this->y/invec.y,this->z/invec.z);
}
//Scaler operators
_inline vector3& operator+=(const float& scaler){//Addition self-assignment
return vector3(this->x+=scaler,this->y+=scaler,this->z+=scaler);
}
_inline vector3& operator-=(const float& scaler){//Subtraction self-assignment
return vector3(this->x-=scaler,this->y-=scaler,this->z-=scaler);
}
_inline vector3& operator*=(const float& scaler){//Multiplication self-assignment
return vector3(this->x*=scaler,this->y*=scaler,this->z*=scaler);
}
_inline vector3& operator*(const float& scalar){
return vector3(this->x*scalar, this->y*scalar, this->z*scalar);
}
//Math methods
_inline vector3 operator^(vector3& invec){//Cross product
return vector3( (this->y*invec.z-this->z*invec.y),
(this->z*invec.x-this->x*invec.z),
(this->x*invec.y-this->y*invec.x));
}
_inline vector3 operator&(vector3& invec){//Dot product
return (this->x*invec.x)+(this->y*invec.y)+(this->z*invec.z);
}
_inline vector3 distance(vector3&);//Distance
_inline void normalize(){
float mag = sqrtf(this->x*this->x+this->y*this->y+this->z*this->z);
this->x/=mag;
this->y/=mag;
this->z/=mag;
}
float x;
float y;
float z;
float getx();
float gety();
float getz();
float getMagnitude();
private:
float mag;
};
Catmull Rom Spline Generation
_inline vector3 createCatmulRomSpline(vector3 P0, vector3 P1, vector3 P2, vector3 P3, float t)
{
float t2 = t*t;
float t3 = t2*t;
vector3 result = ((P1*2) + (P2 - P0) * t + (P0*2 - P1 * 5 + P2*4 - P3)*t2 + (P1*3 - P0- P2*3 + P3) * t3)*0.5f;
return result;
}
I have tried other peoples code and when it comes to outputting the data into the final vector, it outputs bogus values.
You had a number of things wrong with your operators, although most of them weren't used. I modified your class. I notice you have not shown your code for the getx() (etc) members. I implemented those inline, but perhaps they were responsible for the garbage? Here is the test program with my changes. It seems to work fine:
class vector3
{
public:
vector3::vector3()
:x(0), y(0), z(0)
{}
vector3::~vector3()
{}
vector3(float inx,float iny,float inz)
: x(inx), y(iny), z(inz)
{}
//Vector operators
_inline vector3& operator=(const vector3& invec) { // Assignment
x = invec.x;
y = invec.y;
z = invec.z;
return *this;
}
_inline vector3 operator+(const vector3& invec) const {//Addition
return vector3(x+invec.x,y+invec.y,z+invec.z);
}
_inline vector3 operator-(const vector3& invec) const {//Subtraction
return vector3(x-invec.x,y-invec.y,z-invec.z);
}
_inline vector3 operator*(const vector3& invec) const {//Multiplication
return vector3(x*invec.x,y*invec.y,z*invec.z);
}
_inline vector3 operator/(const vector3& invec) const {//Division
return vector3(x/invec.x,y/invec.y,z/invec.z);
}
//scalar operators
_inline vector3& operator+=(const float& scalar){//Addition self-assignment
x+=scalar,y+=scalar,z+=scalar;
return *this;
}
_inline vector3& operator-=(const float& scalar){//Subtraction self-assignment
x-=scalar,y-=scalar,z-=scalar;
return *this;
}
_inline vector3& operator*=(const float& scalar){//Multiplication self-assignment
x*=scalar,y*=scalar,z*=scalar;
return *this;
}
_inline vector3 operator*(const float& scalar) const {
return vector3(x*scalar, y*scalar, z*scalar);
}
//Math methods
_inline vector3 operator^(const vector3& invec) const {//Cross product
return vector3( (y*invec.z-z*invec.y),
(z*invec.x-x*invec.z),
(x*invec.y-y*invec.x));
}
_inline float operator&(const vector3& invec) const {//Dot product
return (x*invec.x)+(y*invec.y)+(z*invec.z);
}
_inline float distance(vector3&) const;//Distance
_inline void normalize(){
float mag = sqrtf(x*x+y*y+z*z);
x/=mag;
y/=mag;
z/=mag;
}
float x;
float y;
float z;
_inline float getx() const { return x; }
_inline float gety() const { return y; }
_inline float getz() const { return z; }
float getMagnitude() const;
};
_inline vector3 createCatmulRomSpline(vector3 P0, vector3 P1, vector3 P2, vector3 P3, float t)
{
float t2 = t*t;
float t3 = t2*t;
vector3 result = ((P1*2) + (P2 - P0) * t + (P0*2 - P1 * 5 + P2*4 - P3)*t2 + (P1*3 - P0- P2*3 + P3) * t3)*0.5f;
return result;
}
int main() {
vector3 vectorOne(0,0,0);
vector3 vectorTwo(5,10,1);
vector3 vectorThree(10,10,2);
vector3 vectorFour(15,0,3);
const int ndiv = 10;
for( int i = 0; i <= ndiv; i++ )
{
float t = (float)i / ndiv;
vector3 SplineVector = createCatmulRomSpline(vectorOne, vectorTwo, vectorThree, vectorFour, t);
cout << "curve pos X: " << SplineVector.getx() << "\n";
cout << "curve pos Y: " << SplineVector.gety() << "\n";
cout << "curve pos Z: " << SplineVector.getz() << "\n\n";
}
return 0;
}
Related
I am studying Shading and how light interacts with objects. I found a great website and wanted to implement knowledge from https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/shading-normals in my own way.
I wrote a code. It is supposed to calculate a facing ratio (cosine of the angle between a normal vector and a light Ray ) and generate a ".BMP" image with that. I took a surface as an object (well, on the image it will be a circle). The idea was to calculate the effect of this ratio on the color of the surface, i.e how light and object interact.
The code is as follows
template <typename T>
class Vec3
{
private:
T x, y, z;
public:
Vec3(): x{0},y{0},z{0} {}
Vec3(T xx): x{xx}, y{xx},z{xx} {}
Vec3(T xx, T yy, T zz): x{xx}, y{yy}, z{zz} {}
friend Vec3<T> operator+(const Vec3<T>& vec1, const Vec3<T>& vec2) { return Vec3<T>(vec1.x + vec2.x, vec1.y + vec2.y, vec1.z + vec2.z); }
friend Vec3<T> operator-(const Vec3<T>& vec1, const Vec3<T>& vec2) { return Vec3<T>(vec1.x - vec2.x, vec1.y - vec2.y, vec1.z - vec2.z); }
friend Vec3<T> operator*(const Vec3<T>& vec1, const Vec3<T>& vec2) { return Vec3<T>(vec1.x * vec2.x, vec1.y * vec2.y, vec1.z * vec2.z); }
friend Vec3<T> operator*(const Vec3<T>& vec1, const T& k) { return Vec3<T>(vec1.x * k, vec1.y * k, vec1.z * k); }
friend Vec3<T> operator/(const Vec3<T>& vec1, const T& k) { return Vec3<T>(vec1.x / k, vec1.y / k, vec1.z / k); }
Vec3<T> operator - () const { return Vec3<T>(-x, -y, -z); }
T dot (const Vec3<T>& v) const { return x * v.x + y * v.y + z * v.z; }
T lengthWithoutRoot() const { return x * x + y * y + z * z; }
T length() const { return sqrt(lengthWithoutRoot()); }
Vec3& normalize()
{
T nor2 = lengthWithoutRoot();
if (nor2 > 0) {
T divider = 1 / sqrt(nor2);
x *= divider, y *= divider, z *= divider;
}
return *this;
}
Vec3<T> reflection(const Vec3<T>& prim,const Vec3<T>& normal) // TO BE CHECKED
{
Vec3<T> reflection = prim - 2 * (prim.dot(normal)) * normal;
return reflection;
}
friend std::ostream& operator<<(std::ostream &out, const Vec3<T>& vec)
{
out << '(' << vec.x << ',' << vec.y << ',' << vec.z << ')';
return out;
}
const T& getX() { return x; }
const T& getY() { return y; }
const T& getZ() { return z; }
};
typedef Vec3<float> Vec3f;
class Sphere
{
private:
Vec3f center;
float radius;
public:
Sphere(const Vec3f& c, const float& r): center{c}, radius{r} {}
bool intersect(const Vec3f& primRay)
{
Vec3f vecRadius = center - primRay;
float distLength = vecRadius.length();
if (distLength > radius)
return false;
return true;
}
bool intersectSurface(const Vec3f& primRay)
{
Vec3f vecRadius = center - primRay;
float distLength = vecRadius.length();
if (distLength == radius)
return true;
return false;
}
float alphaPositive(const Vec3f& p, const Vec3f& source)
{
Vec3f primRay = (source-p).normalize();
Vec3f normal = (p-center).normalize();
float diff = primRay.dot(normal);
return std::max(diff,0.f) ;
}
};
int main()
{
Sphere sphere (Vec3f{ 250.0f, 250.0f, 0.0 }, 150.0f );
Vec3f source{ 100,200,0.0 };
Vec3f color{ 255,255,255 };
std::ofstream file;
file.open("DIF_SPHERE36.ppm");
file << "P6\n" << height << " " << width << "\n255\n";
for (float h = 0; h < 500; ++h)
{
for (float w = 0; w < 500; ++w)
{
Vec3f primRay = { h,w,0.0 };
if (sphere.intersect(primRay))
{
float facingRatio= sphere.alphaPositive(primRay, source);
color = Vec3f{255,0,0}*facingRatio;
file << unsigned char(color.getX()) << unsigned char(color.getY()) << unsigned char(color.getZ());
}
else
file << unsigned char(255) << unsigned char(255) << unsigned char(255);
}
}
file.close();
return 0;
}
However. I get smth strange, even when I try to change 'source' coordinates.
Facing ratio is calculated in alphaPositive function This is what a code must generate according to the idea
Thank you all for your comments.
I made following conclusions:
In function alphaPositive I had to use return std::max(diff,0.1f) instead of return std::max(diff,0.f).
Had to change positioning of the Object and Light by adding z-coordinates.
With that I managed to get a sphere with some effects.
I'm trying to get this code to work and it gives out angles but not the right ones though, they don't add up to 180° and I can't find the problem. I've implemented a Vector2 struct which resembles a two-dimensional Vector and I have been trying to define the ^ operator for it as the function to give the angles between the two vectors.
Thanks for the help in advance!
#include <iostream>
#include <cstdlib>
#include <cmath>
using namespace std;
struct Vector2 {
double x, y;
};
ostream& operator<<(ostream& out, const Vector2& w){
cout << '[' << w.x << " , " << w.y << ']';
return out;
}
double operator*(const Vector2& a, const Vector2& b){
double w = a.x*b.x + a.y*b.y;
return w;
}
double absc(const Vector2& d) {
return sqrt(d*d);
}
constexpr Vector2 operator+(const Vector2& a, const Vector2& b){
Vector2 y{a.x+b.x, a.y+a.y};
return y;
}
constexpr Vector2 operator-(const Vector2& a,const Vector2& b){
Vector2 z{a.x-b.x, a.y-b.y};
return z;
}
double operator^(const Vector2& a,const Vector2& b){
double n = (absc(a))*(absc(b));
double c = acos((a*b)/n);
return c;
}
int main(){
Vector2 a = {1.0, 1.0};
Vector2 b = {4.0, 7.0};
Vector2 c = {-2.0, 5.0};
double d =c^b;
double e = a^c;
double f = a^b;
auto grad = [](double rad) { return rad * (45./atan(1.0)); };
cout<<"Angle Alpha: "<<grad(c^b)<<"rad "<<d<<"\n";
cout<<"Angle Beta: "<<grad(a^c)<<"rad "<<e<<"\n";
cout<<"Angle Gamma: "<<grad(a^b)<<"rad "<<f<<"\n";
return EXIT_SUCCESS;
}
You are not comparing the right angles, try this instead (create the vectors from the points you have):
double d = (a-c)^(a-b);
double e = (b-a)^(b-c);
double f = (c-b)^(c-a);
auto grad = [](double rad) { return rad * (45./atan(1.0)); };
cout<<"Angle Alpha: "<<grad((a-c)^(a-b))<<"rad "<<d;
cout<<"\nAngle Beta: "<<grad((b-a)^(b-c))<<"rad "<<e;
cout<<"\nAngle Gamma: "<<grad((c-a)^(c-b))<<"rad "<<f;
And now you have 180° or pi.
At the moment, I convert the vec3 to vec4, multiply it with the matrix, and then cast it back to vec3
Code:
glm::vec3 transformed = glm::vec3(matrix * glm::vec4(point, 0.0))
It works, but I think it is not the good way to calculate it. Is it possible to apply a mat4 on a vec3, without casting it to vec4 and back?
When working with OpenGL it is very wise to stick with homogeneous coordinates. For 3D space these are 4D vectors where normally the fourth element equals 1. When you do this all your calculations are in 4 dimensional space, so no conversions needed anywhere.
Also note that in your example, you will actually lose any translation that may have been recorded in the transformation matrix. If you want to keep this you'll need to use 1 for the 4th element, not 0.
Appendix F of the Red Book describes how and why homogeneous coordinates are used within OpenGL.
mat4 is a 4 by 4 matrix, so you need a 4 dimension vector to multiply it.
That 4th dimension is really useful for 3D math, to distinguish between 3D space points (1) and 3D vectors (0)
Here I missing lots of operator, but I hope that you get the idea.
class point3
{
public:
vec4 p;
public:
point3();
point3(const point3& rhs);
point3(const point3& rhs);
point3(float x, float y, float z);
point3& operator=(const point3&rhs);
vector3 operator-(const point3&rhs);
point3 operator+(const vector3&rhs);
point3& operator+=(const vector3&rhs);
};
point3::point3():p.x(0), p.y(0), p.z(0), p.w(1)
{
}
point3::point3(const point3& rhs):p(rhs.p)
{
}
point3::point3(const point3& rhs):p(rhs.p)
{
}
point3::point3(float x, float y, float z):p.x(x), p.y(y), p.z(z), p.w(1)
{
}
point3& point3::operator=(const point3&rhs)
{
return (p=rhs.p);
}
vector3 point3::operator-(const point3&rhs)
{
vector3 result;
result.p=(p-rhs.p);
return result;
}
point3 point3::operator+(const vector3&rhs)
{
point3 result;
result.p=(p+rhs.p);
return result;
}
point3& point3::operator+=(const vector3&rhs)
{
p=(p+rhs.p);
return p;
}
class vector3
{
public:
vec4 p;
public:
vector3();
vector3(const vector3& rhs);
vector3(const vector3& rhs);
vector3(float x, float y, float z);
vector3& operator=(const vector3&rhs);
vector3 operator-(const vector3&rhs);
point3 operator-(const point3&rhs);
point3 operator+(const point3&rhs);
vector3 operator+(const vector3&rhs);
vector3& operator+=(const vector3&rhs);
};
vector3::vector3():p.x(0), p.y(0), p.z(0), p.w(0)
{
}
vector3::vector3(const vector3& rhs):p(rhs.p)
{
}
vector3::vector3(const vector3& rhs):p(rhs.p)
{
}
vector3::vector3(float x, float y, float z):p.x(x), p.y(y), p.z(z), p.w(0)
{
}
vector3& vector3::operator=(const vector3&rhs)
{
p=rhs.p;
return p;
}
vector3 vector3::operator-(const vector3&rhs)
{
vector3 result;
result.p=(p-rhs.p);
return result;
}
point3 vector3::operator-(const point3&rhs)
{
point3 result;
result.p=(p-rhs.p);
return result;
}
point3 vector3::operator+(const point3&rhs)
{
point3 result;
result.p=(p+rhs.p);
return result;
}
vector3 vector3::operator+(const vector3&rhs)
{
vector3 result;
result.p=(p+rhs.p);
return result;
}
vector3& vector3::operator+=(const vector3&rhs)
{
p=(p+rhs.p);
return p;
}
Hi I am trying to create a ray tracer that renders a polygonized, triangle-based model.
I have a point 3D struct in point3d.h that holds x,y, and z coordinates.
#ifndef __POINT3D_H__
#define __POINT3D_H__
#include <iostream>
using namespace std;
struct Point3D
{
double x;
double y;
double z;
Point3D() : x(0.0), y(0.0), z(0.0) {}
Point3D(const double & nx, const double & ny, const double & nz) : x(nx), y(ny), z(nz) {}
Point3D operator+(const Point3D & rhs) const {
return Point3D(x + rhs.x, y + rhs.y, z + rhs.z); }
Point3D operator-(const Point3D & rhs) const {
return Point3D(x - rhs.x, y - rhs.y, z - rhs.z); }
Point3D operator*(double val) const {
return Point3D(x * val, y * val, z * val); }
Point3D operator/(double val) const {
return Point3D(x / val, y / val, z / val); }
Point3D operator+=(const Point3D & rhs) {
x += rhs.x; y += rhs.y; z += rhs.z; return *this; }
Point3D operator-=(const Point3D & rhs) {
x -= rhs.x; y -= rhs.y; z -= rhs.z; return *this; }
Point3D operator*=(double val) {
x *= val; y *= val; z *= val; return *this; }
Point3D operator/=(double val) {
x /= val; y /= val; z /= val; return *this; }
void print() {
cout << '(' << x << ',' << y << ',' << z << ')';
}
};
#endif
Here is where I try to use the * operator to multiple two Point3Ds together
Point3D phong(Point3D mColor, Point3D lColor, Point3D L, Point3D N, Point3D R, Point3D V)
{
Point3D k(1.0, 1.0, 1.0);
Point3D ambient = mColor * k.x;
Point3D diffuse_angle = ((N * L) / (length(N) * length(L)));
Point3D diffuse = lColor * k.y * diffuse_angle;
Point3D specular_angle = ((R * V) / (length(R) * length(V)));
double specular_x = pow(specular_angle.x, 100.0);
double specular_y = pow(specular_angle.y, 100.0);
double specular_z = pow(specular_angle.z, 100.0);
Point3D specular_power(specular_x, specular_y, specular_z);
Point3D specular = lColor * k.z * specular_power;
return ambient + (lColor * (diffuse + specular));
}
When I try to multiple two Point3D's together, I am getting a no match error.
Here is where the code fails. I feel like it is a simple mistake but I cannot figure it out. I am including the Point3d header file as follows: #include "point3d.h".
Point3D operator*(double val) const
You have just this version, Point3D * double and nothing else, but you are trying to use this operator for Point3D * Point3D. Point3D is not implicitly constructible from double, so this is why you have compilation error.
Point3D operator*(double val) const {
This is for multiplication Point3D * double. And by
N * L
you are trying to do Point3D * Point3D.
You can rectify this either by providing proper operator* for your class OR provide a conversion from double to your class through single argument constructor. Although I prefer former.
You should need a function like this
Point3D operator *(Point3D &temp) const {
}
Since you don't have function to multiply two 3d points you are getting errors.Try adding this function.
You need a function for the operation Point3D * Point3D, which can't be adapted for the call of Point3D::operator*(double val). Such as:
Point3D operator*(const Point3D & rhs) const {
return Point3D(x * rhs.x, y * rhs.y, z * rhs.z); }
I have just begun dabbling in c++. Is this the correct implementation of a vector 3? I don't want to leak any memory, or do anything that is just bad form in c++. Are my operator overload signatures correct?
class Vector
{
private:
double _x;
double _y;
double _z;
double LengthSquared();
friend std::ostream& operator<<(std::ostream& stream, const Vector& vector);
public:
Vector();
Vector(double x, double y, double z);
~Vector();
Vector(Vector& other);
double GetX() const;
double GetY() const;
double GetZ() const;
double Length();
Vector& Normalize();
double DotProduct(const Vector& vector) const;
Vector CrossProduct(const Vector& vector);
bool operator==(const Vector& vector) const;
bool operator!=(const Vector& other) const;
Vector& operator+=(const Vector& vector);
Vector operator+(const Vector& vector) const;
Vector& operator-=(const Vector& vector);
Vector operator-(const Vector& vector) const;
Vector& operator*=(double val);
double operator*(const Vector& vector) const;
Vector operator*(double val) const;
};
Vector::Vector() : _x(0.0), _y(0.0), _z(0.0)
{
}
Vector::Vector(double x, double y, double z) : _x(x), _y(y), _z(z)
{
}
Vector::~Vector()
{
}
Vector::Vector(Vector& other) : _x(other._x), _y(other._y), _z(other._z)
{
}
double Vector::GetX() const
{
return _x;
}
double Vector::GetY() const
{
return _y;
}
double Vector::GetZ() const
{
return _z;
}
double Vector::Length()
{
return sqrt(LengthSquared());
}
double Vector::LengthSquared()
{
return (_x * _x + _y * _y + _z * _z);
}
Vector& Vector::Normalize()
{
double length = Length();
if(length >0)
{
_x = _x / length;
_y = _y / length;
_z = _z / length;
}
else
{
_x = 0;
_y = 0;
_z = 0;
}
return *this;
}
double Vector::DotProduct(const Vector& vector) const
{
return _x * vector.GetX() + _y * vector.GetY() + _z * vector.GetZ();
}
Vector Vector::CrossProduct(const Vector& vector)
{
double nx = _y * vector.GetZ() - _z * vector.GetY();
double ny = _z * vector.GetX() - _x * vector.GetZ();
double nz = _x * vector.GetY() - _y * vector.GetX();
return Vector(nx, ny, nz);
}
bool Vector::operator==(const Vector& vector) const
{
if(this == &vector)
return true;
if((_x == vector.GetX()) && (_y == vector.GetY()) && (_z == vector.GetZ()))
return true;
return false;
}
bool Vector::operator!=(const Vector& vector) const
{
return !(*this == vector);
}
Vector& Vector::operator+=(const Vector& vector)
{
_x += vector.GetX();
_y += vector.GetY();
_z += vector.GetZ();
return *this;
}
Vector Vector::operator+(const Vector& vector) const
{
return Vector(_x, _y, _z) += vector;
}
Vector& Vector::operator-=(const Vector& vector)
{
_x -= vector.GetX();
_y -= vector.GetY();
_z -= vector.GetZ();
return *this;
}
Vector Vector::operator-(const Vector& vector) const
{
return Vector(_x, _y, _z) -= vector;
}
Vector& Vector::operator*=(double val)
{
_x *= val;
_y *= val;
_z *= val;
return *this;
}
double Vector::operator*(const Vector& vector) const
{
return this->DotProduct(vector);
}
Vector Vector::operator*(double val) const
{
return Vector(_x, _y, _z) *= val;
}
std::ostream& operator<<(std::ostream& stream, const Vector& vector)
{
return stream << "x: " << vector._x << ", y: " << vector._y << ", z: " << vector._z;
}
I would avoid the leading underscores on your attribute names. These particular names are legal but many led by _ are not.
Length and LengthSquared should be const.
The compiler generated copy constructor and destructor will do the right thing - no need to write them yourself and risk a copy-paste error.
Consider making DotProduct and CrossProduct non-member, non-friend "algorithm" functions. Also consider this for the non-mutating operators (which can then be written in the canonical form T operator+(T left, const T& right) { return left += right; }
Strongly consider not providing an operator* at all as it's not intuitive if it means scalar, dot, or cross product. Use named methods instead.