Initial commit

This commit is contained in:
2025-12-17 16:47:48 +00:00
commit 13813f3363
4964 changed files with 1079753 additions and 0 deletions

161
engine/3d/CMakeLists.txt Normal file
View File

@@ -0,0 +1,161 @@
add_library(3D STATIC)
set(SOURCES
src/AABox.cpp
src/AABox.hpp
src/AABSPTree.hpp
src/Array.hpp
src/BinaryFormat.hpp
src/BinaryInput.cpp
src/BinaryInput.hpp
src/BinaryOutput.cpp
src/BinaryOutput.hpp
src/BoundsTrait.hpp
src/Box.cpp
src/Box.hpp
src/BumpMapPreprocess.hpp
src/Capsule.cpp
src/Capsule.hpp
src/CollisionDetection.cpp
src/CollisionDetection.hpp
src/Color1.hpp
src/Color1uint8.hpp
src/Color3.cpp
src/Color3.hpp
src/Color3uint8.cpp
src/Color3uint8.hpp
src/Color4.cpp
src/Color4.hpp
src/Color4uint8.cpp
src/Color4uint8.hpp
src/CompactCFrame.hpp
src/Cone.hpp
src/constants.hpp
src/ConvexPolyhedron.hpp
src/CoordinateFrame.cpp
src/CoordinateFrame.hpp
src/Crypto.cpp
src/Crypto.hpp
src/Cylinder.cpp
src/Cylinder.hpp
src/G3DDebug.hpp
src/debugAssert.cpp
src/debugAssert.hpp
src/debugPrintf.hpp
src/Draw.cpp
src/Draw.hpp
src/DrawAdorn.cpp
src/DrawAdorn.hpp
src/DrawPrimitives.hpp
src/enumclass.hpp
src/EqualsTrait.hpp
src/fileutils.cpp
src/fileutils.hpp
src/format.cpp
src/format.hpp
src/Frustum.cpp
src/Frustum.hpp
src/g3derror.hpp
src/g3dfnmatch.cpp
src/g3dfnmatch.hpp
src/G3DGameUnits.hpp
src/g3dmath.cpp
src/g3dmath.hpp
src/GCamera.cpp
src/GCamera.hpp
src/GImage_bayer.cpp
src/GImage_bmp.cpp
src/GImage_jpeg.cpp
src/GImage_jxl.cpp
src/GImage_png.cpp
src/GImage_tga.cpp
src/GImage.cpp
src/GImage.hpp
src/GLight.cpp
src/GLight.hpp
src/HandleType.hpp
src/HashTrait.hpp
src/HitTest.cpp
src/HitTest.hpp
src/Image1.hpp
src/LightingParameters.cpp
src/LightingParameters.hpp
src/Line.cpp
src/Line.hpp
src/LineSegment.cpp
src/LineSegment.hpp
src/Map2D.hpp
src/Matrix2.hpp
src/Matrix3.cpp
src/Matrix3.hpp
src/Matrix4.cpp
src/Matrix4.hpp
src/MemoryManager.cpp
src/MemoryManager.hpp
src/MeshAlg.hpp
src/MeshBuilder.hpp
src/ParseError.hpp
src/PhysicsFrame.cpp
src/PhysicsFrame.hpp
src/Plane.cpp
src/Plane.hpp
src/platform.hpp
src/PositionTrait.hpp
src/Quat.cpp
src/Quat.hpp
src/Random.cpp
src/Random.hpp
src/Ray.cpp
src/Ray.hpp
src/RbxCamera.cpp
src/RbxCamera.hpp
src/RbxRay.cpp
src/RbxRay.hpp
src/RbxTime.hpp
src/Rect2D.hpp
src/Set.hpp
src/SmallArray.hpp
src/Sphere.cpp
src/Sphere.hpp
src/spline.hpp
src/stringutils.cpp
src/stringutils.hpp
src/System.cpp
src/System.hpp
src/Table.hpp
src/Triangle.cpp
src/Triangle.hpp
src/uint128.cpp
src/uint128.hpp
src/units.hpp
src/UprightFrame.cpp
src/UprightFrame.hpp
src/Vector2.cpp
src/Vector2.hpp
src/Vector2int16.cpp
src/Vector2int16.hpp
src/Vector3.cpp
src/Vector3.hpp
src/Vector3int16.cpp
src/Vector3int16.hpp
src/Vector3int32.hpp
src/Vector4.cpp
src/Vector4.hpp
src/Vector4int8.hpp
src/vectorMath.hpp
src/WrapMode.hpp
)
target_sources(3D PRIVATE ${SOURCES})
target_include_directories(3D
PUBLIC
src
PRIVATE
${THIRD_PARTY_DIR}/BulletPhysics/src
${ENGINE_DIR}/app/src
${ENGINE_DIR}/core/src
${ENGINE_DIR}/gfx/src
)
target_link_libraries(3D $<TARGET_OBJECTS:Core>)

1708
engine/3d/src/AABSPTree.hpp Normal file

File diff suppressed because it is too large Load Diff

355
engine/3d/src/AABox.cpp Normal file
View File

@@ -0,0 +1,355 @@
/**
@file AABox.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2004-01-10
@edited 2006-01-11
*/
#include "platform.hpp"
#include "AABox.hpp"
#include "Box.hpp"
#include "Plane.hpp"
#include "Sphere.hpp"
namespace G3D
{
const AABox& AABox::maxFinite()
{
static const AABox b = AABox(Vector3::minFinite(), Vector3::maxFinite());
return b;
}
const AABox& AABox::large()
{
static const AABox b = AABox(Vector3::minFinite() * 0.5f, Vector3::maxFinite() * 0.5f);
return b;
}
const AABox& AABox::inf()
{
static const AABox b = AABox(-Vector3::inf(), Vector3::inf());
return b;
}
const AABox& AABox::zero()
{
static const AABox b = AABox(Vector3::zero(), Vector3::zero());
return b;
}
void AABox::split(const Vector3::Axis& axis, float location, AABox& low, AABox& high) const
{
// Low, medium, and high along the chosen axis
float L = G3D::min(location, lo[axis]);
float M = G3D::min(G3D::max(location, lo[axis]), hi[axis]);
float H = G3D::max(location, hi[axis]);
// Copy over this box.
high = low = *this;
// Now move the split points along the special axis
low.lo[axis] = L;
low.hi[axis] = M;
high.lo[axis] = M;
high.hi[axis] = H;
}
Vector3 AABox::randomSurfacePoint() const
{
Vector3 extent = hi - lo;
float aXY = extent.x * extent.y;
float aYZ = extent.y * extent.z;
float aZX = extent.z * extent.x;
float r = (float)uniformRandom(0.0f, aXY + aYZ + aZX);
// Choose evenly between positive and negative face planes
float d = ((float)uniformRandom(0, 1) < 0.5f) ? 0.0f : 1.0f;
// The probability of choosing a given face is proportional to
// its area.
if (r < aXY)
{
return lo + Vector3((float)uniformRandom(0.0f, extent.x), (float)uniformRandom(0.0f, extent.y), d * extent.z);
}
else if (r < aYZ)
{
return lo + Vector3(d * extent.x, (float)uniformRandom(0, extent.y), (float)uniformRandom(0, extent.z));
}
else
{
return lo + Vector3((float)uniformRandom(0, extent.x), d * extent.y, (float)uniformRandom(0, extent.z));
}
}
Vector3 AABox::randomInteriorPoint() const
{
return Vector3((float)uniformRandom(lo.x, hi.x), (float)uniformRandom(lo.y, hi.y), (float)uniformRandom(lo.z, hi.z));
}
bool AABox::intersects(const AABox& other) const
{
// Must be overlap along all three axes.
// Try to find a separating axis.
for (int a = 0; a < 3; ++a)
{
// |--------|
// |------|
if ((lo[a] > other.hi[a]) || (hi[a] < other.lo[a]))
{
return false;
}
}
return true;
}
int AABox::dummy = 0;
bool AABox::culledBy(const Array<Plane>& plane, int& cullingPlane, const uint32 _inMask, uint32& childMask) const
{
uint32 inMask = _inMask;
debugAssert(plane.size() < 31);
childMask = 0;
const bool finite = (abs(lo.x) < G3D::finf()) && (abs(hi.x) < G3D::finf()) && (abs(lo.y) < G3D::finf()) && (abs(hi.y) < G3D::finf()) &&
(abs(lo.z) < G3D::finf()) && (abs(hi.z) < G3D::finf());
// See if there is one plane for which all of the
// vertices are in the negative half space.
for (int p = 0; p < plane.size(); ++p)
{
// Only test planes that are not masked
if ((inMask & 1) != 0)
{
Vector3 corner;
int numContained = 0;
int v = 0;
// We can early-out only if we have found one point on each
// side of the plane (i.e. if we are straddling). That
// occurs when (numContained < v) && (numContained > 0)
for (v = 0; (v < 8) && ((numContained == v) || (numContained == 0)); ++v)
{
// Unrolling these 3 if's into a switch decreases performance
// by about 2x
corner.x = (v & 1) ? hi.x : lo.x;
corner.y = (v & 2) ? hi.y : lo.y;
corner.z = (v & 4) ? hi.z : lo.z;
if (finite)
{ // this branch is highly predictable
if (plane[p].halfSpaceContainsFinite(corner))
{
++numContained;
}
}
else
{
if (plane[p].halfSpaceContains(corner))
{
++numContained;
}
}
}
if (numContained == 0)
{
// Plane p culled the box
cullingPlane = p;
// The caller should not recurse into the children,
// since the parent is culled. If they do recurse,
// make them only test against this one plane, which
// will immediately cull the volume.
childMask = 1 << p;
return true;
}
else if (numContained < v)
{
// The bounding volume straddled the plane; we have
// to keep testing against this plane
childMask |= (1 << p);
}
}
// Move on to the next bit.
inMask = inMask >> 1;
}
// None of the planes could cull this box
cullingPlane = -1;
return false;
}
bool AABox::culledBy(const Array<Plane>& plane, int& cullingPlane, const uint32 _inMask) const
{
uint32 inMask = _inMask;
debugAssert(plane.size() < 31);
const bool finite = (abs(lo.x) < G3D::finf()) && (abs(hi.x) < G3D::finf()) && (abs(lo.y) < G3D::finf()) && (abs(hi.y) < G3D::finf()) &&
(abs(lo.z) < G3D::finf()) && (abs(hi.z) < G3D::finf());
// See if there is one plane for which all of the
// vertices are in the negative half space.
for (int p = 0; p < plane.size(); ++p)
{
// Only test planes that are not masked
if ((inMask & 1) != 0)
{
bool culled = true;
Vector3 corner;
int v;
// Assume this plane culls all points. See if there is a point
// not culled by the plane... early out when at least one point
// is in the positive half space.
for (v = 0; (v < 8) && culled; ++v)
{
// Unrolling these 3 if's into a switch decreases performance
// by about 2x
corner.x = (v & 1) ? hi.x : lo.x;
corner.y = (v & 2) ? hi.y : lo.y;
corner.z = (v & 4) ? hi.z : lo.z;
if (finite)
{ // this branch is highly predictable
culled = !plane[p].halfSpaceContainsFinite(corner);
}
else
{
culled = !plane[p].halfSpaceContains(corner);
}
}
if (culled)
{
// Plane p culled the box
cullingPlane = p;
return true;
}
}
// Move on to the next bit.
inMask = inMask >> 1;
}
// None of the planes could cull this box
cullingPlane = -1;
return false;
}
bool AABox::intersects(const class Sphere& sphere) const
{
double d = 0;
// find the square of the distance
// from the sphere to the box
for (int i = 0; i < 3; ++i)
{
if (sphere.center[i] < lo[i])
{
d += square(sphere.center[i] - lo[i]);
}
else if (sphere.center[i] > hi[i])
{
d += square(sphere.center[i] - hi[i]);
}
}
return d <= square(sphere.radius);
}
Vector3 AABox::corner(int index) const
{
// default constructor inits all components to 0
Vector3 v;
switch (index)
{
case 0:
v.x = lo.x;
v.y = lo.y;
v.z = hi.z;
break;
case 1:
v.x = hi.x;
v.y = lo.y;
v.z = hi.z;
break;
case 2:
v.x = hi.x;
v.y = hi.y;
v.z = hi.z;
break;
case 3:
v.x = lo.x;
v.y = hi.y;
v.z = hi.z;
break;
case 4:
v.x = lo.x;
v.y = lo.y;
v.z = lo.z;
break;
case 5:
v.x = hi.x;
v.y = lo.y;
v.z = lo.z;
break;
case 6:
v.x = hi.x;
v.y = hi.y;
v.z = lo.z;
break;
case 7:
v.x = lo.x;
v.y = hi.y;
v.z = lo.z;
break;
default:
debugAssertM(false, "Invalid corner index");
break;
}
return v;
}
} // namespace G3D

302
engine/3d/src/AABox.hpp Normal file
View File

@@ -0,0 +1,302 @@
/**
@file AABox.h
Axis-aligned box class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2004-01-10
@edited 2009-02-10
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_AABOX_H
#define G3D_AABOX_H
#include "platform.hpp"
#include "Vector3.hpp"
#include "G3DDebug.hpp"
#include "Array.hpp"
#include "Plane.hpp"
namespace G3D
{
/**
An axis-aligned box.
*/
class AABox
{
private:
friend class Intersect;
/** Optional argument placeholder */
static int dummy;
Vector3 lo;
Vector3 hi;
public:
/** Does not initialize the fields */
inline AABox() {}
/**
Constructs a zero-area AABox at v.
*/
inline explicit AABox(const Vector3& v)
{
lo = hi = v;
}
/** Assumes that low is less than or equal to high along each dimension.
To have this automatically enforced, use
<code>AABox(low.min(high), low.max(high));</code>
*/
inline AABox(const Vector3& low, const Vector3& high)
{
set(low, high);
}
/** Assumes that low is less than or equal to high along each dimension.
*/
inline void set(const Vector3& low, const Vector3& high)
{
debugAssert((low.x <= high.x) && (low.y <= high.y) && (low.z <= high.z));
lo = low;
hi = high;
}
/**
Grows to include the bounds of a
*/
inline void merge(const AABox& a)
{
lo = lo.min(a.lo);
hi = hi.max(a.hi);
}
inline void merge(const Vector3& a)
{
lo = lo.min(a);
hi = hi.max(a);
}
inline bool isFinite() const
{
return lo.isFinite() && hi.isFinite();
}
inline const Vector3& low() const
{
return lo;
}
inline const Vector3& high() const
{
return hi;
}
/**
The largest possible finite box.
*/
static const AABox& maxFinite();
/** A large finite box. This is smaller than FLT_MAX
because it leaves room to add boxes together. */
static const AABox& large();
static const AABox& inf();
static const AABox& zero();
/**
Returns the centroid of the box.
*/
inline Vector3 center() const
{
return (lo + hi) * 0.5;
}
Vector3 corner(int index) const;
/**
Distance from corner(0) to the next corner along axis a.
*/
inline float extent(int a) const
{
debugAssert(a < 3);
return hi[a] - lo[a];
}
inline Vector3 extent() const
{
return hi - lo;
}
/**
Splits the box into two AABoxes along the specified axis. low contains
the part that was closer to negative infinity along axis, high contains
the other part. Either may have zero volume.
*/
void split(const Vector3::Axis& axis, float location, AABox& low, AABox& high) const;
/**
Conservative culling test for up to 32 planes.
Returns true if there exists a <CODE>plane[p]</CODE> for
which the entire object is in the negative half space
(opposite the plane normal).
<CODE>testMask</CODE> and <CODE>childMask</CODE>
are used for optimizing bounding volume hierarchies.
The version of this method that produces childMask
is slower than the version without; it should only
be used for parent nodes.
@param cullingPlaneIndex The index of the first plane for which
the entire object is in the negative half-space. The function
exits early when one plane is found. -1 when the function
returns false (i.e. when no plane culls the whole object).
@param testMask If bit <I>p</I> is 0, the
bounding volume automatically passes the culling test for
<CODE>plane[p]</CODE> (i.e. it is known that the volume
is entirely within the positive half space). The function
must return false if testMask is 0 and test all planes
when testMask is -1 (0xFFFFFFFF).
@param childMask Test mask for the children of this volume.
*/
bool culledBy(const Array<Plane>& plane, int32& cullingPlaneIndex, const uint32 testMask, uint32& childMask) const;
/**
Conservative culling test that does not produce a mask for children.
*/
bool culledBy(const Array<Plane>& plane, int32& cullingPlaneIndex = dummy, const uint32 testMask = 0xFFFFFFFF) const;
/** less than or equal to containment */
inline bool contains(const AABox& other) const
{
return (other.hi.x <= hi.x) && (other.hi.y <= hi.y) && (other.hi.z <= hi.z) && (other.lo.x >= lo.x) && (other.lo.y >= lo.y) &&
(other.lo.z >= lo.z);
}
inline bool contains(const Vector3& point) const
{
return (point.x >= lo.x) && (point.y >= lo.y) && (point.z >= lo.z) && (point.x <= hi.x) && (point.y <= hi.y) && (point.z <= hi.z);
}
inline float area() const
{
Vector3 diag = hi - lo;
return 2.0f * (diag.x * diag.y + diag.y * diag.z + diag.x * diag.z);
}
inline float volume() const
{
Vector3 diag = hi - lo;
return diag.x * diag.y * diag.z;
}
Vector3 randomInteriorPoint() const;
Vector3 randomSurfacePoint() const;
/** Returns true if there is any overlap */
bool intersects(const AABox& other) const;
/** Returns true if there is any overlap.
@cite Jim Arvo's algorithm from Graphics Gems II*/
bool intersects(const class Sphere& other) const;
/** Return the intersection of the two boxes */
AABox intersect(const AABox& other) const
{
Vector3 H = hi.min(other.hi);
Vector3 L = lo.max(other.lo).min(H);
return AABox(L, H);
}
inline size_t hashCode() const
{
return lo.hashCode() + hi.hashCode();
}
inline bool operator==(const AABox& b) const
{
return (lo == b.lo) && (hi == b.hi);
}
inline bool operator!=(const AABox& b) const
{
return !((lo == b.lo) && (hi == b.hi));
}
inline AABox operator+(const Vector3& v) const
{
AABox out;
out.lo = lo + v;
out.hi = hi + v;
return out;
}
inline AABox operator-(const Vector3& v) const
{
AABox out;
out.lo = lo - v;
out.hi = hi - v;
return out;
}
void getBounds(AABox& out) const
{
out = *this;
}
};
// Adapted from geometrictools.com implementation for AABbox
// Real DistPoint3Box3<Real>::GetSquared
inline float ClosestSqDistanceToAABB(const Vector3& point, const Vector3& center, const Vector3& extents)
{
Vector3 diff = point - center;
float sqDistance = 0;
float delta;
for (int i = 0; i < 3; i++)
{
float axisdistance = diff[i]; // closest[i] = diff.Dot(mBox->Axis[i]); with axis aligned;
if (axisdistance < -extents[i])
{
delta = axisdistance + extents[i];
sqDistance += delta * delta;
}
if (axisdistance > extents[i])
{
delta = axisdistance - extents[i];
sqDistance += delta * delta;
}
}
return sqDistance;
}
} // namespace G3D
template<>
struct HashTrait<G3D::AABox>
{
static size_t hashCode(const G3D::AABox& key)
{
return key.hashCode();
}
};
#endif

1434
engine/3d/src/Array.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,176 @@
/**
@file BinaryFormat.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@author 2005-06-03
@edited 2005-06-03
Copyright 2000-2005, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_BINARYFORMAT_H
#define G3D_BINARYFORMAT_H
#include "platform.hpp"
#include "g3dmath.hpp"
namespace G3D
{
class Vector2;
class Vector2int16;
class Vector3;
class Vector3int16;
class Vector4;
class Vector4int16;
class Color3;
class Color3uint8;
class Color4;
class Color4uint8;
/**
Some values like float16 and int128 have no current CPU data structure that implements them but are useful
for file formats and for GPUs.
CHUNK_BINFMT data follows the protocol.
*/
// Must be packed int 16 bits for the chunk reader
// We can't name these just "INT8" etc. because some libraries #define names like that
enum BinaryFormat
{
FIRST_BINFMT = 1000,
BOOL8_BINFMT,
UINT8_BINFMT,
INT8_BINFMT,
UINT16_BINFMT,
INT16_BINFMT,
UINT32_BINFMT,
INT32_BINFMT,
UINT64_BINFMT,
INT64_BINFMT,
UINT128_BINFMT,
INT128_BINFMT,
FLOAT16_BINFMT,
FLOAT32_BINFMT,
FLOAT64_BINFMT,
VECTOR2_BINFMT,
VECTOR2INT16_BINFMT,
VECTOR3_BINFMT,
VECTOR3INT16_BINFMT,
VECTOR4_BINFMT,
VECTOR4INT16_BINFMT,
COLOR3_BINFMT,
COLOR3UINT8_BINFMT,
COLOR3INT16_BINFMT,
COLOR4_BINFMT,
COLOR4UINT8_BINFMT,
COLOR4INT16_BINFMT,
STRING_BINFMT,
STRINGEVEN_BINFMT,
STRING8_BINFMT,
STRING16_BINFMT,
STRING32_BINFMT,
CHUNK_BINFMT,
CUSTOM_BINFMT,
LAST_BINFMT
};
} // namespace G3D
/** A macro that maps G3D types to format constants.
(e.g. binaryFormatOf(Vector3) == VECTOR3_BINFMT).
*/
// This implementation is designed to meet the following constraints:
// 1. Work around the many MSVC++ partial template bugs
// 2. Work for primitive types (e.g. int)
#define binaryFormatOf(T) (G3D::_internal::_BinaryFormat<T>::x())
namespace G3D
{
namespace _internal
{
template<class T>
class _BinaryFormat
{
public:
static BinaryFormat x()
{
return CUSTOM_BINFMT;
}
};
} // namespace _internal
} // namespace G3D
/**
Macro to declare the underlying format (as will be returned by glFormatOf)
of a type. For example,
<PRE>
DECLARE_BINARYFORMATOF(Vector4, VECTOR4_BINFMT)
</PRE>
Use this so you can make vertex arrays of your own classes and not just
the standard ones.
*/
#define DECLARE_BINARYFORMATOF(CType, EnumType) \
namespace G3D \
{ \
namespace _internal \
{ \
template<> \
class _BinaryFormat<CType> \
{ \
public: \
static BinaryFormat x() \
{ \
return EnumType; \
} \
}; \
} \
}
DECLARE_BINARYFORMATOF(bool, BOOL8_BINFMT)
DECLARE_BINARYFORMATOF(uint8, UINT8_BINFMT)
DECLARE_BINARYFORMATOF(int8, INT8_BINFMT)
DECLARE_BINARYFORMATOF(uint16, UINT16_BINFMT)
DECLARE_BINARYFORMATOF(int16, INT16_BINFMT)
DECLARE_BINARYFORMATOF(uint32, UINT32_BINFMT)
DECLARE_BINARYFORMATOF(int32, INT32_BINFMT)
DECLARE_BINARYFORMATOF(uint64, UINT64_BINFMT)
DECLARE_BINARYFORMATOF(int64, INT64_BINFMT)
DECLARE_BINARYFORMATOF(float32, FLOAT32_BINFMT)
DECLARE_BINARYFORMATOF(float64, FLOAT64_BINFMT)
DECLARE_BINARYFORMATOF(Vector2, VECTOR2_BINFMT)
DECLARE_BINARYFORMATOF(Vector2int16, VECTOR2INT16_BINFMT)
DECLARE_BINARYFORMATOF(Vector3, VECTOR3_BINFMT)
DECLARE_BINARYFORMATOF(Vector3int16, VECTOR3INT16_BINFMT)
DECLARE_BINARYFORMATOF(Vector4, VECTOR4_BINFMT)
DECLARE_BINARYFORMATOF(Vector4int16, VECTOR4INT16_BINFMT)
DECLARE_BINARYFORMATOF(Color3, COLOR3_BINFMT)
DECLARE_BINARYFORMATOF(Color3uint8, COLOR3UINT8_BINFMT)
DECLARE_BINARYFORMATOF(Color4, COLOR4_BINFMT)
DECLARE_BINARYFORMATOF(Color4uint8, COLOR4UINT8_BINFMT)
namespace G3D
{
/** Returns -1 if the format is custom, otherwise the byte size
of a single element in this format.*/
int32 byteSize(BinaryFormat f);
} // namespace G3D
#endif

View File

@@ -0,0 +1,500 @@
/**
@file BinaryInput.cpp
@author Morgan McGuire, graphics3d.com
Copyright 2001-2007, Morgan McGuire. All rights reserved.
@created 2001-08-09
@edited 2010-03-05
<PRE>
{
BinaryOutput b("c:/tmp/test.b", BinaryOutput::LITTLE_ENDIAN);
float f = 3.1415926;
int i = 1027221;
std::string s = "Hello World!";
b.writeFloat32(f);
b.writeInt32(i);
b.writeString(s);
b.commit();
BinaryInput in("c:/tmp/test.b", BinaryInput::LITTLE_ENDIAN);
debugAssert(f == in.readFloat32());
int ii = in.readInt32();
debugAssert(i == ii);
debugAssert(s == in.readString());
}
</PRE>
*/
#include "platform.hpp"
#include "BinaryInput.hpp"
#include "Array.hpp"
#include "fileutils.hpp"
#include <zlib.h>
#include <stdexcept>
#include <cstring>
namespace G3D
{
void BinaryInput::readBool8(std::vector<bool>& out, int64 n)
{
out.resize((int)n);
// std::vector optimizes bool in a way that prevents fast reading
for (int64 i = 0; i < n; ++i)
{
out[i] = readBool8();
}
}
void BinaryInput::readBool8(Array<bool>& out, int64 n)
{
out.resize(n);
readBool8(out.begin(), n);
}
#define IMPLEMENT_READER(ucase, lcase) \
void BinaryInput::read##ucase(std::vector<lcase>& out, int64 n) \
{ \
out.resize(n); \
read##ucase(&out[0], n); \
} \
\
\
void BinaryInput::read##ucase(Array<lcase>& out, int64 n) \
{ \
out.resize(n); \
read##ucase(out.begin(), n); \
}
IMPLEMENT_READER(UInt8, uint8)
IMPLEMENT_READER(Int8, int8)
IMPLEMENT_READER(UInt16, uint16)
IMPLEMENT_READER(Int16, int16)
IMPLEMENT_READER(UInt32, uint32)
IMPLEMENT_READER(Int32, int32)
IMPLEMENT_READER(UInt64, uint64)
IMPLEMENT_READER(Int64, int64)
IMPLEMENT_READER(Float32, float32)
IMPLEMENT_READER(Float64, float64)
#undef IMPLEMENT_READER
// Data structures that are one byte per element can be
// directly copied, regardles of endian-ness.
#define IMPLEMENT_READER(ucase, lcase) \
void BinaryInput::read##ucase(lcase* out, int64 n) \
{ \
if (sizeof(lcase) == 1) \
{ \
readBytes(out, n); \
} \
else \
{ \
for (int64 i = 0; i < n; ++i) \
{ \
out[i] = read##ucase(); \
} \
} \
}
IMPLEMENT_READER(Bool8, bool)
IMPLEMENT_READER(UInt8, uint8)
IMPLEMENT_READER(Int8, int8)
#undef IMPLEMENT_READER
#define IMPLEMENT_READER(ucase, lcase) \
void BinaryInput::read##ucase(lcase* out, int64 n) \
{ \
if (m_swapBytes) \
{ \
for (int64 i = 0; i < n; ++i) \
{ \
out[i] = read##ucase(); \
} \
} \
else \
{ \
readBytes(out, sizeof(lcase) * n); \
} \
}
IMPLEMENT_READER(UInt16, uint16)
IMPLEMENT_READER(Int16, int16)
IMPLEMENT_READER(UInt32, uint32)
IMPLEMENT_READER(Int32, int32)
IMPLEMENT_READER(UInt64, uint64)
IMPLEMENT_READER(Int64, int64)
IMPLEMENT_READER(Float32, float32)
IMPLEMENT_READER(Float64, float64)
#undef IMPLEMENT_READER
void BinaryInput::loadIntoMemory(int64 startPosition, int64 minLength)
{
assert(false); // should not be using this
}
const bool BinaryInput::NO_COPY = false;
static bool needSwapBytes(G3DEndian fileEndian)
{
return (fileEndian != System::machineEndian());
}
/** Helper used by the constructors for decompression */
static uint32 readUInt32(const uint8* data, bool swapBytes)
{
if (swapBytes)
{
uint8 out[4];
out[0] = data[3];
out[1] = data[2];
out[2] = data[1];
out[3] = data[0];
return *((uint32*)out);
}
else
{
return *((uint32*)data);
}
}
void BinaryInput::setEndian(G3DEndian e)
{
m_fileEndian = e;
m_swapBytes = needSwapBytes(m_fileEndian);
}
BinaryInput::BinaryInput(const uint8* data, int64 dataLen, G3DEndian dataEndian, bool compressed, bool copyMemory)
: m_filename("<memory>")
, m_bitPos(0)
, m_bitString(0)
, m_beginEndBits(0)
, m_alreadyRead(0)
, m_bufferLength(0)
, m_pos(0)
{
setEndian(dataEndian);
m_freeBuffer = copyMemory || compressed;
if (compressed)
{
// Read the decompressed size from the first 4 bytes
m_length = G3D::readUInt32(data, m_swapBytes);
debugAssert(m_freeBuffer);
m_buffer = (uint8*)malloc(m_length); // was: alignedMalloc()
unsigned long L = m_length;
// Decompress with zlib
int64 result = uncompress(m_buffer, (unsigned long*)&L, data + 4, dataLen - 4);
m_length = L;
m_bufferLength = L;
debugAssert(result == Z_OK);
(void)result;
}
else
{
m_length = dataLen;
m_bufferLength = m_length;
if (!copyMemory)
{
debugAssert(!m_freeBuffer);
m_buffer = const_cast<uint8*>(data);
}
else
{
debugAssert(m_freeBuffer);
m_buffer = (uint8*)malloc(m_length); // was: alignedMalloc()
System::memcpy(m_buffer, data, dataLen);
}
}
}
BinaryInput::BinaryInput(const std::string& filename, G3DEndian fileEndian, bool compressed)
: m_filename(filename)
, m_bitPos(0)
, m_bitString(0)
, m_beginEndBits(0)
, m_alreadyRead(0)
, m_length(0)
, m_bufferLength(0)
, m_buffer(NULL)
, m_pos(0)
, m_freeBuffer(true)
{
assert(false); // we should not be using this
}
void BinaryInput::decompress()
{
// Decompress
// Use the existing buffer as the source, allocate
// a new buffer to use as the destination.
assert(false); // we should not be using this
}
void BinaryInput::readBytes(void* bytes, int64 n)
{
prepareToRead(n);
debugAssert(isValidPointer(bytes));
memcpy(bytes, m_buffer + m_pos, n);
m_pos += n;
}
BinaryInput::~BinaryInput()
{
if (m_freeBuffer)
{
free(m_buffer); // was: alignedFree()
}
m_buffer = NULL;
}
uint64 BinaryInput::readUInt64()
{
prepareToRead(8);
uint8 out[8];
if (m_swapBytes)
{
out[0] = m_buffer[m_pos + 7];
out[1] = m_buffer[m_pos + 6];
out[2] = m_buffer[m_pos + 5];
out[3] = m_buffer[m_pos + 4];
out[4] = m_buffer[m_pos + 3];
out[5] = m_buffer[m_pos + 2];
out[6] = m_buffer[m_pos + 1];
out[7] = m_buffer[m_pos + 0];
}
else
{
*(uint64*)out = *(uint64*)(m_buffer + m_pos);
}
m_pos += 8;
return *(uint64*)out;
}
std::string BinaryInput::readString(int64 n)
{
assert(false); // we should not be using this
std::string dummystring;
return dummystring;
}
std::string BinaryInput::readString()
{
int64 n = 0;
if ((m_pos + m_alreadyRead + n) < (m_length - 1))
{
prepareToRead(1);
}
if (((m_pos + m_alreadyRead + n) < (m_length - 1)) && (m_buffer[m_pos + n] != '\0'))
{
++n;
while (((m_pos + m_alreadyRead + n) < (m_length - 1)) && (m_buffer[m_pos + n] != '\0'))
{
prepareToRead(1);
++n;
}
}
// Consume NULL
++n;
return readString(n);
}
static bool isNewline(char c)
{
return c == '\n' || c == '\r';
}
std::string BinaryInput::readStringNewline()
{
int64 n = 0;
if ((m_pos + m_alreadyRead + n) < (m_length - 1))
{
prepareToRead(1);
}
if (((m_pos + m_alreadyRead + n) < (m_length - 1)) && !isNewline(m_buffer[m_pos + n]))
{
++n;
while (((m_pos + m_alreadyRead + n) < (m_length - 1)) && !isNewline(m_buffer[m_pos + n]))
{
prepareToRead(1);
++n;
}
}
const std::string s = readString(n);
// Consume the newline
char firstNLChar = readUInt8();
// Consume the 2nd newline
if (isNewline(m_buffer[m_pos + 1]) && (m_buffer[m_pos + 1] != firstNLChar))
{
readUInt8();
}
return s;
}
std::string BinaryInput::readStringEven()
{
std::string x = readString();
if (hasMore() && (G3D::isOdd(x.length() + 1)))
{
skip(1);
}
return x;
}
std::string BinaryInput::readString32()
{
int len = readUInt32();
return readString(len);
}
Vector4 BinaryInput::readVector4()
{
float x = readFloat32();
float y = readFloat32();
float z = readFloat32();
float w = readFloat32();
return Vector4(x, y, z, w);
}
Vector3 BinaryInput::readVector3()
{
float x = readFloat32();
float y = readFloat32();
float z = readFloat32();
return Vector3(x, y, z);
}
Vector2 BinaryInput::readVector2()
{
float x = readFloat32();
float y = readFloat32();
return Vector2(x, y);
}
Color4 BinaryInput::readColor4()
{
float r = readFloat32();
float g = readFloat32();
float b = readFloat32();
float a = readFloat32();
return Color4(r, g, b, a);
}
Color3 BinaryInput::readColor3()
{
float r = readFloat32();
float g = readFloat32();
float b = readFloat32();
return Color3(r, g, b);
}
void BinaryInput::beginBits()
{
debugAssert(m_beginEndBits == 0);
m_beginEndBits = 1;
m_bitPos = 0;
debugAssertM(hasMore(), "Can't call beginBits when at the end of a file");
m_bitString = readUInt8();
}
uint32 BinaryInput::readBits(int numBits)
{
debugAssert(m_beginEndBits == 1);
uint32 out = 0;
const int total = numBits;
while (numBits > 0)
{
if (m_bitPos > 7)
{
// Consume a new byte for reading. We do this at the beginning
// of the loop so that we don't try to read past the end of the file.
m_bitPos = 0;
m_bitString = readUInt8();
}
// Slide the lowest bit of the bitString into
// the correct position.
out |= (m_bitString & 1) << (total - numBits);
// Shift over to the next bit
m_bitString = m_bitString >> 1;
++m_bitPos;
--numBits;
}
return out;
}
void BinaryInput::endBits()
{
debugAssert(m_beginEndBits == 1);
if (m_bitPos == 0)
{
// Put back the last byte we read
--m_pos;
}
m_beginEndBits = 0;
m_bitPos = 0;
}
} // namespace G3D

View File

@@ -0,0 +1,472 @@
/**
@file BinaryInput.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2001-08-09
@edited 2010-03-19
Copyright 2000-2010, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_BinaryInput_h
#define G3D_BinaryInput_h
#ifdef _MSC_VER
// Disable conditional expression is constant, which occurs incorrectly on inlined functions
#pragma warning(push)
#pragma warning(disable : 4127)
#endif
#include <assert.h>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include "platform.hpp"
#include "Array.hpp"
#include "Color4.hpp"
#include "Color3.hpp"
#include "Vector4.hpp"
#include "Vector3.hpp"
#include "Vector2.hpp"
#include "g3dmath.hpp"
#include "G3DDebug.hpp"
#include "System.hpp"
namespace G3D
{
#if defined(G3D_WIN32) || defined(G3D_LINUX)
// Allow writing of integers to non-word aligned locations.
// This is legal on x86, but not on other platforms.
#define G3D_ALLOW_UNALIGNED_WRITES
#endif
/**
Sequential or random access byte-order independent binary file access.
Files compressed with zlib and beginning with an unsigned 32-bit int
size are transparently decompressed when the compressed = true flag is
specified to the constructor.
For every readX method there are also versions that operate on a whole
Array, std::vector, or C-array. e.g. readFloat32(Array<float32>& array, n)
These methods resize the array or std::vector to the appropriate size
before reading. For a C-array, they require the pointer to reference
a memory block at least large enough to hold <I>n</I> elements.
Most classes define serialize/deserialize methods that use BinaryInput,
BinaryOutput, TextInput, and TextOutput. There are text serializer
functions for primitive types (e.g. int, std::string, float, double) but not
binary serializers-- you <B>must</b> call the BinaryInput::readInt32 or
other appropriate function. This is because it would be very hard to
debug the error sequence: <CODE>serialize(1.0, bo); ... float f; deserialize(f, bi);</CODE>
in which a double is serialized and then deserialized as a float.
*/
class BinaryInput
{
private:
// The initial buffer will be no larger than this, but
// may grow if a large memory read occurs. 50 MB
enum
{
INITIAL_BUFFER_LENGTH = 50000000
};
/**
is the file big or little endian
*/
G3DEndian m_fileEndian;
std::string m_filename;
bool m_swapBytes;
/** Next position to read from in bitString during readBits. */
int m_bitPos;
/** Bits currently being read by readBits.
Contains at most 8 (low) bits. Note that
beginBits/readBits actually consumes one extra byte, which
will be restored by writeBits.*/
uint32 m_bitString;
/** 1 when between beginBits and endBits, 0 otherwise. */
int m_beginEndBits;
/** When operating on huge files, we cannot load the whole file into memory.
This is the file position to which buffer[0] corresponds.
*/
int64 m_alreadyRead;
/**
Length of the entire file, in bytes.
For the length of the buffer, see bufferLength
*/
int64 m_length;
/** Length of the array referenced by buffer. May go past the end of the file!*/
int64 m_bufferLength;
uint8* m_buffer;
/**
Next byte in file, relative to buffer.
*/
int64 m_pos;
/**
When true, the buffer is freed in the destructor.
*/
bool m_freeBuffer;
/** Ensures that we are able to read at least minLength from startPosition (relative
to start of file). */
void loadIntoMemory(int64 startPosition, int64 minLength = 0);
/** Verifies that at least this number of bytes can be read.*/
inline void prepareToRead(int64 nbytes)
{
debugAssertM(m_length > 0, m_filename + " not found or corrupt.");
debugAssertM(m_pos + nbytes + m_alreadyRead <= m_length, "Read past end of file.");
if (m_pos + nbytes > m_bufferLength)
{
loadIntoMemory(m_pos + m_alreadyRead, nbytes);
}
}
// Not implemented on purpose, don't use
BinaryInput(const BinaryInput&);
BinaryInput& operator=(const BinaryInput&);
bool operator==(const BinaryInput&);
/** Buffer is compressed; replace it with a decompressed version */
void decompress();
public:
/** false, constant to use with the copyMemory option */
static const bool NO_COPY;
/**
If the file cannot be opened, a zero length buffer is presented.
Automatically opens files that are inside zipfiles.
@param compressed Set to true if and only if the file was
compressed using BinaryOutput's zlib compression. This has
nothing to do with whether the input is in a zipfile.
*/
BinaryInput(const std::string& filename, G3DEndian fileEndian, bool compressed = false);
/**
Creates input stream from an in memory source.
Unless you specify copyMemory = false, the data is copied
from the pointer, so you may deallocate it as soon as the
object is constructed. It is an error to specify copyMemory = false
and compressed = true.
To decompress part of a file, you can follow the following paradigm:
<PRE>
BinaryInput master(...);
// read from master to point where compressed data exists.
BinaryInput subset(master.getCArray() + master.getPosition(),
master.length() - master.getPosition(),
master.endian(), true, true);
// Now read from subset (it is ok for master to go out of scope)
</PRE>
*/
BinaryInput(const uint8* data, int64 dataLen, G3DEndian dataEndian, bool compressed = false, bool copyMemory = true);
virtual ~BinaryInput();
/** Change the endian-ness of the file. This only changes the
interpretation of the file for future read calls; the
underlying data is unmodified.*/
void setEndian(G3DEndian endian);
G3DEndian endian() const
{
return m_fileEndian;
}
std::string getFilename() const
{
return m_filename;
}
/**
Returns a pointer to the internal memory buffer.
May throw an exception for huge files.
*/
const uint8* getCArray() const
{
if (m_alreadyRead > 0)
{
throw "Cannot getCArray for a huge file";
}
return m_buffer;
}
/**
Performs bounds checks in debug mode. [] are relative to
the start of the file, not the current position.
Seeks to the new position before reading (and leaves
that as the current position)
*/
inline uint8 operator[](int64 n)
{
setPosition(n);
return readUInt8();
}
/**
Returns the length of the file in bytes.
*/
inline int64 getLength() const
{
return m_length;
}
inline int64 size() const
{
return getLength();
}
/**
Returns the current byte position in the file,
where 0 is the beginning and getLength() - 1 is the end.
*/
inline int64 getPosition() const
{
return m_pos + m_alreadyRead;
}
/**
Sets the position. Cannot set past length.
May throw a char* when seeking backwards more than 10 MB on a huge file.
*/
inline void setPosition(int64 p)
{
debugAssertM(p <= m_length, "Read past end of file");
m_pos = p - m_alreadyRead;
if ((m_pos < 0) || (m_pos > m_bufferLength))
{
loadIntoMemory(m_pos + m_alreadyRead);
}
}
/**
Goes back to the beginning of the file.
*/
inline void reset()
{
setPosition(0);
}
inline int8 readInt8()
{
prepareToRead(1);
return m_buffer[m_pos++];
}
inline bool readBool8()
{
return (readInt8() != 0);
}
inline uint8 readUInt8()
{
prepareToRead(1);
return ((uint8*)m_buffer)[m_pos++];
}
uint16 inline readUInt16()
{
prepareToRead(2);
m_pos += 2;
if (m_swapBytes)
{
uint8 out[2];
out[0] = m_buffer[m_pos - 1];
out[1] = m_buffer[m_pos - 2];
return *(uint16*)out;
}
else
{
#ifdef G3D_ALLOW_UNALIGNED_WRITES
return *(uint16*)(&m_buffer[m_pos - 2]);
#else
uint8 out[2];
out[0] = m_buffer[m_pos - 2];
out[1] = m_buffer[m_pos - 1];
return *(uint16*)out;
#endif
}
}
inline int16 readInt16()
{
uint16 a = readUInt16();
return *(int16*)&a;
}
inline uint32 readUInt32()
{
prepareToRead(4);
m_pos += 4;
if (m_swapBytes)
{
uint8 out[4];
out[0] = m_buffer[m_pos - 1];
out[1] = m_buffer[m_pos - 2];
out[2] = m_buffer[m_pos - 3];
out[3] = m_buffer[m_pos - 4];
return *(uint32*)out;
}
else
{
#ifdef G3D_ALLOW_UNALIGNED_WRITES
return *(uint32*)(&m_buffer[m_pos - 4]);
#else
uint8 out[4];
out[0] = m_buffer[m_pos - 4];
out[1] = m_buffer[m_pos - 3];
out[2] = m_buffer[m_pos - 2];
out[3] = m_buffer[m_pos - 1];
return *(uint32*)out;
#endif
}
}
inline int32 readInt32()
{
uint32 a = readUInt32();
return *(int32*)&a;
}
uint64 readUInt64();
inline int64 readInt64()
{
uint64 a = readUInt64();
return *(int64*)&a;
}
inline float32 readFloat32()
{
union
{
uint32 a;
float32 b;
};
a = readUInt32();
return b;
}
inline float64 readFloat64()
{
union
{
uint64 a;
float64 b;
};
a = readUInt64();
return b;
}
void readBytes(void* bytes, int64 n);
/**
Reads an n character string. The string is not
required to end in NULL in the file but will
always be a proper std::string when returned.
*/
std::string readString(int64 n);
/**
Reads until NULL or the end of the file is encountered.
*/
std::string readString();
/** Reads until \r, \r\n, \n\r, \n or the end of the file is encountered. Consumes the newline.*/
std::string readStringNewline();
/**
Reads until NULL or the end of the file is encountered.
If the string has odd length (including NULL), reads
another byte.
*/
std::string readStringEven();
std::string readString32();
Vector4 readVector4();
Vector3 readVector3();
Vector2 readVector2();
Color4 readColor4();
Color3 readColor3();
/**
Skips ahead n bytes.
*/
inline void skip(int64 n)
{
setPosition(m_pos + m_alreadyRead + n);
}
/**
Returns true if the position is not at the end of the file
*/
inline bool hasMore() const
{
return m_pos + m_alreadyRead < m_length;
}
/** Prepares for bit reading via readBits. Only readBits can be
called between beginBits and endBits without corrupting the
data stream. */
void beginBits();
/** Can only be called between beginBits and endBits */
uint32 readBits(int numBits);
/** Ends bit-reading. */
void endBits();
#define DECLARE_READER(ucase, lcase) \
void read##ucase(lcase* out, int64 n); \
void read##ucase(std::vector<lcase>& out, int64 n); \
void read##ucase(Array<lcase>& out, int64 n);
DECLARE_READER(Bool8, bool)
DECLARE_READER(UInt8, uint8)
DECLARE_READER(Int8, int8)
DECLARE_READER(UInt16, uint16)
DECLARE_READER(Int16, int16)
DECLARE_READER(UInt32, uint32)
DECLARE_READER(Int32, int32)
DECLARE_READER(UInt64, uint64)
DECLARE_READER(Int64, int64)
DECLARE_READER(Float32, float32)
DECLARE_READER(Float64, float64)
#undef DECLARE_READER
};
} // namespace G3D
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif

View File

@@ -0,0 +1,502 @@
/**
@file BinaryOutput.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
Copyright 2002-2010, Morgan McGuire, All rights reserved.
@created 2002-02-20
@edited 2010-03-17
*/
#include "platform.hpp"
#include "BinaryOutput.hpp"
#include "fileutils.hpp"
#include "stringutils.hpp"
#include "Array.hpp"
#include <zlib.h>
#include <stdexcept>
#include <cstring>
#if defined(G3D_LINUX) || defined(G3D_IOS) || defined(G3D_ANDROID) // Aya
#include <errno.h>
#endif
// Largest memory buffer that the system will use for writing to
// disk. After this (or if the system runs out of memory)
// chunks of the file will be dumped to disk.
//
// Currently 400 MB
#define MAX_BINARYOUTPUT_BUFFER_SIZE 400000000
namespace G3D
{
void BinaryOutput::writeBool8(const std::vector<bool>& out, int n)
{
for (int i = 0; i < n; ++i)
{
writeBool8(out[i]);
}
}
void BinaryOutput::writeBool8(const Array<bool>& out, int n)
{
writeBool8(out.getCArray(), n);
}
#define IMPLEMENT_WRITER(ucase, lcase) \
void BinaryOutput::write##ucase(const std::vector<lcase>& out, int n) \
{ \
write##ucase(&out[0], n); \
} \
\
\
void BinaryOutput::write##ucase(const Array<lcase>& out, int n) \
{ \
write##ucase(out.getCArray(), n); \
}
IMPLEMENT_WRITER(UInt8, uint8)
IMPLEMENT_WRITER(Int8, int8)
IMPLEMENT_WRITER(UInt16, uint16)
IMPLEMENT_WRITER(Int16, int16)
IMPLEMENT_WRITER(UInt32, uint32)
IMPLEMENT_WRITER(Int32, int32)
IMPLEMENT_WRITER(UInt64, uint64)
IMPLEMENT_WRITER(Int64, int64)
IMPLEMENT_WRITER(Float32, float32)
IMPLEMENT_WRITER(Float64, float64)
#undef IMPLEMENT_WRITER
// Data structures that are one byte per element can be
// directly copied, regardles of endian-ness.
#define IMPLEMENT_WRITER(ucase, lcase) \
void BinaryOutput::write##ucase(const lcase* out, int n) \
{ \
if (sizeof(lcase) == 1) \
{ \
writeBytes((void*)out, n); \
} \
else \
{ \
for (int i = 0; i < n; ++i) \
{ \
write##ucase(out[i]); \
} \
} \
}
IMPLEMENT_WRITER(Bool8, bool)
IMPLEMENT_WRITER(UInt8, uint8)
IMPLEMENT_WRITER(Int8, int8)
#undef IMPLEMENT_WRITER
#define IMPLEMENT_WRITER(ucase, lcase) \
void BinaryOutput::write##ucase(const lcase* out, int n) \
{ \
if (m_swapBytes) \
{ \
for (int i = 0; i < n; ++i) \
{ \
write##ucase(out[i]); \
} \
} \
else \
{ \
writeBytes((const void*)out, sizeof(lcase) * n); \
} \
}
IMPLEMENT_WRITER(UInt16, uint16)
IMPLEMENT_WRITER(Int16, int16)
IMPLEMENT_WRITER(UInt32, uint32)
IMPLEMENT_WRITER(Int32, int32)
IMPLEMENT_WRITER(UInt64, uint64)
IMPLEMENT_WRITER(Int64, int64)
IMPLEMENT_WRITER(Float32, float32)
IMPLEMENT_WRITER(Float64, float64)
#undef IMPLEMENT_WRITER
void BinaryOutput::reallocBuffer(size_t bytes, size_t oldBufferLen)
{
// debugPrintf("reallocBuffer(%d, %d)\n", bytes, oldBufferLen);
size_t newBufferLen = (int)(m_bufferLen * 1.5) + 100;
uint8* newBuffer = NULL;
if ((m_filename == "<memory>") || (newBufferLen < MAX_BINARYOUTPUT_BUFFER_SIZE))
{
// We're either writing to memory (in which case we *have* to try and allocate)
// or we've been asked to allocate a reasonable size buffer.
// debugPrintf(" realloc(%d)\n", newBufferLen);
newBuffer = (uint8*)System::realloc(m_buffer, newBufferLen);
if (newBuffer != NULL)
{
m_maxBufferLen = newBufferLen;
}
}
if ((newBuffer == NULL) && (bytes > 0))
{
// Realloc failed; we're probably out of memory. Back out
// the entire call and try to dump some data to disk.
m_bufferLen = oldBufferLen;
reserveBytesWhenOutOfMemory(bytes);
}
else
{
m_buffer = newBuffer;
debugAssert(isValidHeapPointer(m_buffer));
}
}
void BinaryOutput::reserveBytesWhenOutOfMemory(size_t bytes)
{
assert(false); // we should not be using this
}
BinaryOutput::BinaryOutput()
{
m_alreadyWritten = 0;
m_swapBytes = false;
m_pos = 0;
m_filename = "<memory>";
m_buffer = NULL;
m_bufferLen = 0;
m_maxBufferLen = 0;
m_beginEndBits = 0;
m_bitString = 0;
m_bitPos = 0;
m_ok = true;
m_committed = false;
}
BinaryOutput::BinaryOutput(const std::string& filename, G3DEndian fileEndian)
{
m_pos = 0;
m_alreadyWritten = 0;
setEndian(fileEndian);
m_filename = filename;
m_buffer = NULL;
m_bufferLen = 0;
m_maxBufferLen = 0;
m_beginEndBits = 0;
m_bitString = 0;
m_bitPos = 0;
m_committed = false;
m_ok = true;
/** Verify ability to write to disk */
commit(false);
m_committed = false;
}
void BinaryOutput::reset()
{
debugAssert(m_beginEndBits == 0);
alwaysAssertM(m_filename == "<memory>", "Can only reset a BinaryOutput that writes to memory.");
// Do not reallocate, just clear the size of the buffer.
m_pos = 0;
m_alreadyWritten = 0;
m_bufferLen = 0;
m_beginEndBits = 0;
m_bitString = 0;
m_bitPos = 0;
m_committed = false;
}
BinaryOutput::~BinaryOutput()
{
debugAssert((m_buffer == NULL) || isValidHeapPointer(m_buffer));
System::free(m_buffer);
m_buffer = NULL;
m_bufferLen = 0;
m_maxBufferLen = 0;
}
void BinaryOutput::setEndian(G3DEndian fileEndian)
{
m_fileEndian = fileEndian;
m_swapBytes = (fileEndian != System::machineEndian());
}
bool BinaryOutput::ok() const
{
return m_ok;
}
void BinaryOutput::compress()
{
if (m_alreadyWritten > 0)
{
throw "Cannot compress huge files (part of this file has already been written to disk).";
}
// Old buffer size
int L = m_bufferLen;
uint8* convert = (uint8*)&L;
// Zlib requires the output buffer to be this big
unsigned long newSize = iCeil(m_bufferLen * 1.01) + 12;
uint8* temp = (uint8*)System::malloc(newSize);
int result = compress2(temp, &newSize, m_buffer, m_bufferLen, 9);
debugAssert(result == Z_OK);
(void)result;
// Write the header
if (m_swapBytes)
{
m_buffer[0] = convert[3];
m_buffer[1] = convert[2];
m_buffer[2] = convert[1];
m_buffer[3] = convert[0];
}
else
{
m_buffer[0] = convert[0];
m_buffer[1] = convert[1];
m_buffer[2] = convert[2];
m_buffer[3] = convert[3];
}
// Write the data
if ((int64)newSize + 4 > (int64)m_maxBufferLen)
{
m_maxBufferLen = newSize + 4;
m_buffer = (uint8*)System::realloc(m_buffer, m_maxBufferLen);
}
m_bufferLen = newSize + 4;
System::memcpy(m_buffer + 4, temp, newSize);
m_pos = m_bufferLen;
System::free(temp);
}
void BinaryOutput::commit(bool flush)
{
assert(false); // we should not be using this
}
void BinaryOutput::commit(uint8* out)
{
debugAssertM(!m_committed, "Cannot commit twice");
m_committed = true;
System::memcpy(out, m_buffer, m_bufferLen);
}
void BinaryOutput::writeUInt16(uint16 u)
{
reserveBytes(2);
uint8* convert = (uint8*)&u;
if (m_swapBytes)
{
m_buffer[m_pos] = convert[1];
m_buffer[m_pos + 1] = convert[0];
}
else
{
*(uint16*)(m_buffer + m_pos) = u;
}
m_pos += 2;
}
void BinaryOutput::writeUInt32(uint32 u)
{
reserveBytes(4);
uint8* convert = (uint8*)&u;
debugAssert(m_beginEndBits == 0);
if (m_swapBytes)
{
m_buffer[m_pos] = convert[3];
m_buffer[m_pos + 1] = convert[2];
m_buffer[m_pos + 2] = convert[1];
m_buffer[m_pos + 3] = convert[0];
}
else
{
*(uint32*)(m_buffer + m_pos) = u;
}
m_pos += 4;
}
void BinaryOutput::writeUInt64(uint64 u)
{
reserveBytes(8);
uint8* convert = (uint8*)&u;
if (m_swapBytes)
{
m_buffer[m_pos] = convert[7];
m_buffer[m_pos + 1] = convert[6];
m_buffer[m_pos + 2] = convert[5];
m_buffer[m_pos + 3] = convert[4];
m_buffer[m_pos + 4] = convert[3];
m_buffer[m_pos + 5] = convert[2];
m_buffer[m_pos + 6] = convert[1];
m_buffer[m_pos + 7] = convert[0];
}
else
{
*(uint64*)(m_buffer + m_pos) = u;
}
m_pos += 8;
}
void BinaryOutput::writeString(const char* s)
{
// +1 is because strlen doesn't count the null
int len = strlen(s) + 1;
debugAssert(m_beginEndBits == 0);
reserveBytes(len);
System::memcpy(m_buffer + m_pos, s, len);
m_pos += len;
}
void BinaryOutput::writeStringEven(const char* s)
{
// +1 is because strlen doesn't count the null
int len = strlen(s) + 1;
reserveBytes(len);
System::memcpy(m_buffer + m_pos, s, len);
m_pos += len;
// Pad with another NULL
if ((len % 2) == 1)
{
writeUInt8(0);
}
}
void BinaryOutput::writeString32(const char* s)
{
writeUInt32(strlen(s) + 1);
writeString(s);
}
void BinaryOutput::writeVector4(const Vector4& v)
{
writeFloat32(v.x);
writeFloat32(v.y);
writeFloat32(v.z);
writeFloat32(v.w);
}
void BinaryOutput::writeVector3(const Vector3& v)
{
writeFloat32(v.x);
writeFloat32(v.y);
writeFloat32(v.z);
}
void BinaryOutput::writeVector2(const Vector2& v)
{
writeFloat32(v.x);
writeFloat32(v.y);
}
void BinaryOutput::writeColor4(const Color4& v)
{
writeFloat32(v.r);
writeFloat32(v.g);
writeFloat32(v.b);
writeFloat32(v.a);
}
void BinaryOutput::writeColor3(const Color3& v)
{
writeFloat32(v.r);
writeFloat32(v.g);
writeFloat32(v.b);
}
void BinaryOutput::beginBits()
{
debugAssertM(m_beginEndBits == 0, "Already in beginBits...endBits");
m_bitString = 0x00;
m_bitPos = 0;
m_beginEndBits = 1;
}
void BinaryOutput::writeBits(uint32 value, int numBits)
{
while (numBits > 0)
{
// Extract the current bit of value and
// insert it into the current byte
m_bitString |= (value & 1) << m_bitPos;
++m_bitPos;
value = value >> 1;
--numBits;
if (m_bitPos > 7)
{
// We've reached the end of this byte
writeUInt8(m_bitString);
m_bitString = 0x00;
m_bitPos = 0;
}
}
}
void BinaryOutput::endBits()
{
debugAssertM(m_beginEndBits == 1, "Not in beginBits...endBits");
if (m_bitPos > 0)
{
writeUInt8(m_bitString);
}
m_bitString = 0;
m_bitPos = 0;
m_beginEndBits = 0;
}
} // namespace G3D

View File

@@ -0,0 +1,448 @@
/**
@file BinaryOutput.h
@maintainer Morgan McGuire, graphics3d.com
@created 2001-08-09
@edited 2008-01-24
Copyright 2000-2006, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_BINARYOUTPUT_H
#define G3D_BINARYOUTPUT_H
#include "platform.hpp"
#include <assert.h>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include "Color4.hpp"
#include "Color3.hpp"
#include "Vector4.hpp"
#include "Vector3.hpp"
#include "Vector2.hpp"
#include "g3dmath.hpp"
#include "G3DDebug.hpp"
#include "BinaryInput.hpp"
#include "System.hpp"
#ifdef _MSC_VER
#pragma warning(push)
// Conditional is constant (wrong in inline)
#pragma warning(disable : 4127)
#endif
namespace G3D
{
/**
Sequential or random access byte-order independent binary file access.
The compress() call can be used to compress with zlib.
Any method call can trigger an out of memory error (thrown as char*)
when writing to "<memory>" instead of a file.
Compressed writing and seeking backwards is not supported for huge files
(i.e., BinaryOutput may have to dump the contents to disk if they
exceed available RAM).
*/
class BinaryOutput
{
private:
std::string m_filename;
bool m_committed;
/** 0 outside of beginBits...endBits, 1 inside */
int m_beginEndBits;
/** The current string of bits being built up by beginBits...endBits.
This string is treated semantically, as if the lowest bit was
on the left and the highest was on the right.*/
int8 m_bitString;
/** Position (from the lowest bit) currently used in bitString.*/
int m_bitPos;
// True if the file endianess does not match the machine endian
bool m_swapBytes;
G3DEndian m_fileEndian;
uint8* m_buffer;
/** Size of the elements used */
int m_bufferLen;
/** Underlying size of memory allocaded */
int m_maxBufferLen;
/** Next byte in file */
int m_pos;
/** is this initialized? */
bool m_init;
/** Number of bytes already written to the file.*/
size_t m_alreadyWritten;
bool m_ok;
void reserveBytesWhenOutOfMemory(size_t bytes);
void reallocBuffer(size_t bytes, size_t oldBufferLen);
/**
Make sure at least bytes can be written, resizing if
necessary.
*/
inline void reserveBytes(int bytes)
{
debugAssert(bytes > 0);
size_t oldBufferLen = (size_t)m_bufferLen;
m_bufferLen = iMax(m_bufferLen, (m_pos + bytes));
if (m_bufferLen > m_maxBufferLen)
{
reallocBuffer(bytes, oldBufferLen);
}
}
// Not implemented on purpose, don't use
BinaryOutput(const BinaryOutput&);
BinaryOutput& operator=(const BinaryOutput&);
bool operator==(const BinaryOutput&);
public:
/**
You must call setEndian() if you use this (memory) constructor.
*/
BinaryOutput();
/**
Doesn't actually open the file; commit() does that.
Use "<memory>" as the filename if you're going to commit
to memory.
*/
BinaryOutput(const std::string& filename, G3DEndian fileEndian);
~BinaryOutput();
/** Compresses the data in the buffer in place,
preceeding it with a little-endian uint32 indicating
the uncompressed size.
Call immediately before commit().
Cannot be used for huge files (ones where the data
was already written to disk)-- will throw char*.
*/
void compress();
/** True if no errors have been encountered.*/
bool ok() const;
/**
Returns a pointer to the internal memory buffer.
*/
inline const uint8* getCArray() const
{
return m_buffer;
}
void setEndian(G3DEndian fileEndian);
G3DEndian endian() const
{
return m_fileEndian;
}
std::string getFilename() const
{
return m_filename;
}
/**
Write the bytes to disk. It is ok to call this
multiple times; it will just overwrite the previous file.
Parent directories are created as needed if they do
not exist.
<B>Not</B> called from the destructor; you must call
it yourself.
@param flush If true (default) the file is ready for reading when the method returns, otherwise
the method returns immediately and writes the file in the background.
*/
void commit(bool flush = true);
/**
Write the bytes to memory (which must be of
at least size() bytes).
*/
void commit(uint8*);
/**
A memory BinaryOutput may be reset so that it can be written to again
without allocating new memory. The underlying array will not be deallocated,
but the reset structure will act like a newly intialized one.
*/
void reset();
inline int length() const
{
return (int)m_bufferLen + (int)m_alreadyWritten;
}
inline int size() const
{
return length();
}
/**
Sets the length of the file to n, padding
with 0's past the current end. Does not
change the position of the next byte to be
written unless n < size().
Throws char* when resetting a huge file to be shorter
than its current length.
*/
inline void setLength(int n)
{
n = n - (int)m_alreadyWritten;
if (n < 0)
{
throw "Cannot resize huge files to be shorter.";
}
if (n < m_bufferLen)
{
m_pos = n;
}
if (n > m_bufferLen)
{
reserveBytes(n - m_bufferLen);
}
}
/**
Returns the current byte position in the file,
where 0 is the beginning and getLength() - 1 is the end.
*/
inline int64 position() const
{
return (int64)m_pos + (int64)m_alreadyWritten;
}
/**
Sets the position. Can set past length, in which case
the file is padded with zeros up to one byte before the
next to be written.
May throw a char* exception when seeking backwards on a huge file.
*/
inline void setPosition(int64 p)
{
p = p - (int64)m_alreadyWritten;
if (p > m_bufferLen)
{
setLength((int)(p + (int64)m_alreadyWritten));
}
if (p < 0)
{
throw "Cannot seek more than 10 MB backwards on huge files.";
}
m_pos = (int)p;
}
void writeBytes(const void* b, int count)
{
reserveBytes(count);
debugAssert(m_pos >= 0);
debugAssert(m_bufferLen >= count);
System::memcpy(m_buffer + m_pos, b, count);
m_pos += count;
}
/**
Writes a signed 8-bit integer to the current position.
*/
inline void writeInt8(int8 i)
{
reserveBytes(1);
m_buffer[m_pos] = *(uint8*)&i;
m_pos++;
}
inline void writeBool8(bool b)
{
writeInt8(b ? 1 : 0);
}
inline void writeUInt8(uint8 i)
{
reserveBytes(1);
m_buffer[m_pos] = i;
m_pos++;
}
void writeUInt16(uint16 u);
inline void writeInt16(int16 i)
{
writeUInt16(*(uint16*)&i);
}
void writeUInt32(uint32 u);
inline void writeInt32(int32 i)
{
debugAssert(m_beginEndBits == 0);
writeUInt32(*(uint32*)&i);
}
void writeUInt64(uint64 u);
inline void writeInt64(int64 i)
{
writeUInt64(*(uint64*)&i);
}
inline void writeFloat32(float32 f)
{
debugAssert(m_beginEndBits == 0);
union
{
float32 a;
uint32 b;
};
a = f;
writeUInt32(b);
}
inline void writeFloat64(float64 f)
{
union
{
float64 a;
uint64 b;
};
a = f;
writeUInt64(b);
}
/**
Write a string with NULL termination.
*/
inline void writeString(const std::string& s)
{
writeString(s.c_str());
}
void writeString(const char* s);
/**
Write a string, ensuring that the total length
including NULL is even.
*/
void writeStringEven(const std::string& s)
{
writeStringEven(s.c_str());
}
void writeStringEven(const char* s);
void writeString32(const char* s);
/**
Write a string with a 32-bit length field in front
of it.
*/
void writeString32(const std::string& s)
{
writeString32(s.c_str());
}
void writeVector4(const Vector4& v);
void writeVector3(const Vector3& v);
void writeVector2(const Vector2& v);
void writeColor4(const Color4& v);
void writeColor3(const Color3& v);
/**
Skips ahead n bytes.
*/
inline void skip(int n)
{
if (m_pos + n > m_bufferLen)
{
setLength((int)m_pos + (int)m_alreadyWritten + n);
}
m_pos += n;
}
/** Call before a series of BinaryOutput::writeBits calls. Only writeBits
can be called between beginBits and endBits without corrupting the stream.*/
void beginBits();
/** Write numBits from bitString to the output stream. Bits are numbered from
low to high.
Can only be
called between beginBits and endBits. Bits written are semantically
little-endian, regardless of the actual endian-ness of the system. That is,
<CODE>writeBits(0xABCD, 16)</CODE> writes 0xCD to the first byte and
0xAB to the second byte. However, if used with BinaryInput::readBits, the ordering
is transparent to the caller.
*/
void writeBits(uint32 bitString, int numBits);
/** Call after a series of BinaryOutput::writeBits calls. This will
finish out with zeros the last byte into which bits were written.*/
void endBits();
#define DECLARE_WRITER(ucase, lcase) \
void write##ucase(const lcase* out, int n); \
void write##ucase(const std::vector<lcase>& out, int n); \
void write##ucase(const Array<lcase>& out, int n);
DECLARE_WRITER(Bool8, bool)
DECLARE_WRITER(UInt8, uint8)
DECLARE_WRITER(Int8, int8)
DECLARE_WRITER(UInt16, uint16)
DECLARE_WRITER(Int16, int16)
DECLARE_WRITER(UInt32, uint32)
DECLARE_WRITER(Int32, int32)
DECLARE_WRITER(UInt64, uint64)
DECLARE_WRITER(Int64, int64)
DECLARE_WRITER(Float32, float32)
DECLARE_WRITER(Float64, float64)
#undef DECLARE_WRITER
};
} // namespace G3D
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif

View File

@@ -0,0 +1,21 @@
/**
@file BoundsTrait.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2008-10-01
@edited 2008-10-01
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_BOUNDSTRAIT_H
#define G3D_BOUNDSTRAIT_H
#include "platform.hpp"
template<typename Value>
struct BoundsTrait
{
};
#endif

368
engine/3d/src/Box.cpp Normal file
View File

@@ -0,0 +1,368 @@
/**
@file Box.cpp
Box class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2001-06-02
@edited 2006-02-05
*/
#include "Box.hpp"
#include "G3DDebug.hpp"
#include "Plane.hpp"
#include "AABox.hpp"
#include "CoordinateFrame.hpp"
namespace G3D
{
/**
Sets a field on four vertices. Used by the constructor.
*/
#define setMany(i0, i1, i2, i3, field, extreme) _corner[i0].field = _corner[i1].field = _corner[i2].field = _corner[i3].field = (extreme).field
Box::Box() {}
Box::Box(const AABox& b)
{
init(b.low(), b.high());
}
Box::Box(const Vector3& min, const Vector3& max)
{
init(min.min(max), min.max(max));
}
void Box::init(const Vector3& min, const Vector3& max)
{
debugAssert((min.x <= max.x) && (min.y <= max.y) && (min.z <= max.z));
setMany(0, 1, 2, 3, z, max);
setMany(4, 5, 6, 7, z, min);
setMany(1, 2, 5, 6, x, max);
setMany(0, 3, 4, 7, x, min);
setMany(3, 2, 6, 7, y, max);
setMany(0, 1, 5, 4, y, min);
_extent = max - min;
_axis[0] = Vector3::unitX();
_axis[1] = Vector3::unitY();
_axis[2] = Vector3::unitZ();
if (_extent.isFinite())
{
_volume = _extent.x * _extent.y * _extent.z;
}
else
{
_volume = G3D::finf();
}
debugAssert(!isNaN(_extent.x));
_area = 2 * (_extent.x * _extent.y + _extent.y * _extent.z + _extent.z * _extent.x);
_center = (max + min) * 0.5f;
// If the extent is infinite along an axis, make the center zero to avoid NaNs
for (int i = 0; i < 3; ++i)
{
if (!G3D::isFinite(_extent[i]))
{
_center[i] = 0.0f;
}
}
}
float Box::volume() const
{
return _volume;
}
float Box::area() const
{
return _area;
}
void Box::getLocalFrame(CoordinateFrame& frame) const
{
frame.rotation = Matrix3(_axis[0][0], _axis[1][0], _axis[2][0], _axis[0][1], _axis[1][1], _axis[2][1], _axis[0][2], _axis[1][2], _axis[2][2]);
frame.translation = _center;
}
CoordinateFrame Box::localFrame() const
{
CoordinateFrame out;
getLocalFrame(out);
return out;
}
void Box::getFaceCorners(int f, Vector3& v0, Vector3& v1, Vector3& v2, Vector3& v3) const
{
switch (f)
{
case 0:
v0 = _corner[0];
v1 = _corner[1];
v2 = _corner[2];
v3 = _corner[3];
break;
case 1:
v0 = _corner[1];
v1 = _corner[5];
v2 = _corner[6];
v3 = _corner[2];
break;
case 2:
v0 = _corner[7];
v1 = _corner[6];
v2 = _corner[5];
v3 = _corner[4];
break;
case 3:
v0 = _corner[2];
v1 = _corner[6];
v2 = _corner[7];
v3 = _corner[3];
break;
case 4:
v0 = _corner[3];
v1 = _corner[7];
v2 = _corner[4];
v3 = _corner[0];
break;
case 5:
v0 = _corner[1];
v1 = _corner[0];
v2 = _corner[4];
v3 = _corner[5];
break;
default:
debugAssert((f >= 0) && (f < 6));
}
}
int Box::dummy = 0;
bool Box::culledBy(const Array<Plane>& plane, int& cullingPlane, const uint32 _inMask, uint32& childMask) const
{
uint32 inMask = _inMask;
assert(plane.size() < 31);
childMask = 0;
// See if there is one plane for which all of the
// vertices are in the negative half space.
for (int p = 0; p < plane.size(); ++p)
{
// Only test planes that are not masked
if ((inMask & 1) != 0)
{
Vector3 corner;
int numContained = 0;
int v = 0;
// We can early-out only if we have found one point on each
// side of the plane (i.e. if we are straddling). That
// occurs when (numContained < v) && (numContained > 0)
for (v = 0; (v < 8) && ((numContained == v) || (numContained == 0)); ++v)
{
if (plane[p].halfSpaceContains(_corner[v]))
{
++numContained;
}
}
if (numContained == 0)
{
// Plane p culled the box
cullingPlane = p;
// The caller should not recurse into the children,
// since the parent is culled. If they do recurse,
// make them only test against this one plane, which
// will immediately cull the volume.
childMask = 1 << p;
return true;
}
else if (numContained < v)
{
// The bounding volume straddled the plane; we have
// to keep testing against this plane
childMask |= (1 << p);
}
}
// Move on to the next bit.
inMask = inMask >> 1;
}
// None of the planes could cull this box
cullingPlane = -1;
return false;
}
bool Box::culledBy(const Array<Plane>& plane, int& cullingPlane, const uint32 _inMask) const
{
uint32 inMask = _inMask;
assert(plane.size() < 31);
// See if there is one plane for which all of the
// vertices are in the negative half space.
for (int p = 0; p < plane.size(); ++p)
{
// Only test planes that are not masked
if ((inMask & 1) != 0)
{
bool culled = true;
int v;
// Assume this plane culls all points. See if there is a point
// not culled by the plane... early out when at least one point
// is in the positive half space.
for (v = 0; (v < 8) && culled; ++v)
{
culled = !plane[p].halfSpaceContains(corner(v));
}
if (culled)
{
// Plane p culled the box
cullingPlane = p;
return true;
}
}
// Move on to the next bit.
inMask = inMask >> 1;
}
// None of the planes could cull this box
cullingPlane = -1;
return false;
}
bool Box::contains(const Vector3& point) const
{
// Form axes from three edges, transform the point into that
// space, and perform 3 interval tests
Vector3 u = _corner[4] - _corner[0];
Vector3 v = _corner[3] - _corner[0];
Vector3 w = _corner[1] - _corner[0];
Matrix3 M = Matrix3(u.x, v.x, w.x, u.y, v.y, w.y, u.z, v.z, w.z);
// M^-1 * (point - _corner[0]) = point in unit cube's object space
// compute the inverse of M
Vector3 osPoint = M.inverse() * (point - _corner[0]);
return (osPoint.x >= 0) && (osPoint.y >= 0) && (osPoint.z >= 0) && (osPoint.x <= 1) && (osPoint.y <= 1) && (osPoint.z <= 1);
}
#undef setMany
void Box::getRandomSurfacePoint(Vector3& P, Vector3& N) const
{
float aXY = _extent.x * _extent.y;
float aYZ = _extent.y * _extent.z;
float aZX = _extent.z * _extent.x;
float r = (float)uniformRandom(0, aXY + aYZ + aZX);
// Choose evenly between positive and negative face planes
float d = (uniformRandom(0, 1) < 0.5f) ? -1.0f : 1.0f;
// The probability of choosing a given face is proportional to
// its area.
if (r < aXY)
{
P = _axis[0] * (float)uniformRandom(-0.5, 0.5) * _extent.x + _axis[1] * (float)uniformRandom(-0.5, 0.5) * _extent.y + _center +
_axis[2] * d * _extent.z * 0.5f;
N = _axis[2] * d;
}
else if (r < aYZ)
{
P = _axis[1] * (float)uniformRandom(-0.5, 0.5) * _extent.y + _axis[2] * (float)uniformRandom(-0.5, 0.5) * _extent.z + _center +
_axis[0] * d * _extent.x * 0.5f;
N = _axis[0] * d;
}
else
{
P = _axis[2] * (float)uniformRandom(-0.5, 0.5) * _extent.z + _axis[0] * (float)uniformRandom(-0.5, 0.5) * _extent.x + _center +
_axis[1] * d * _extent.y * 0.5f;
N = _axis[1] * d;
}
}
Vector3 Box::randomInteriorPoint() const
{
Vector3 sum = _center;
for (int a = 0; a < 3; ++a)
{
sum += _axis[a] * (float)uniformRandom(-0.5, 0.5) * _extent[a];
}
return sum;
}
Box Box::inf()
{
return Box(-Vector3::inf(), Vector3::inf());
}
void Box::getBounds(class AABox& aabb) const
{
Vector3 lo = _corner[0];
Vector3 hi = lo;
for (int v = 1; v < 8; ++v)
{
const Vector3& C = _corner[v];
lo = lo.min(C);
hi = hi.max(C);
}
aabb = AABox(lo, hi);
}
} // namespace G3D

177
engine/3d/src/Box.hpp Normal file
View File

@@ -0,0 +1,177 @@
/**
@file Box.h
Box class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@cite Portions based on Dave Eberly's Magic Software Library at <A HREF="http://www.magic-software.com">http://www.magic-software.com</A>
@created 2001-06-02
@edited 2007-06-05
Copyright 2000-2006, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_BOX_H
#define G3D_BOX_H
#include "platform.hpp"
#include "Vector3.hpp"
#include "Array.hpp"
#include "Plane.hpp"
namespace G3D
{
class CoordinateFrame;
/**
An arbitrary 3D box, useful as a bounding box.
To construct a box from a coordinate frame, center and extent, use the idiom:
<CODE>Box box = cframe.toObjectSpace(Box(center - extent/2, center + extent/2));</CODE>
*/
class Box
{
private:
static int32 dummy;
friend class CoordinateFrame;
/**
<PRE>
3 2 7 6
0 1 4 5
front back (seen through front)
</PRE>
*/
Vector3 _corner[8];
/**
Unit axes.
*/
Vector3 _axis[3];
Vector3 _center;
/**
Extent along each axis.
*/
Vector3 _extent;
float _area;
float _volume;
void init(const Vector3& min, const Vector3& max);
public:
/**
Does not initialize the fields.
*/
Box();
/**
Constructs a box from two opposite corners.
*/
Box(const Vector3& min, const Vector3& max);
static Box inf();
Box(const class AABox& b);
/**
Returns the object to world transformation for
this box. localFrame().worldToObject(...) takes
objects into the space where the box axes are
(1,0,0), (0,1,0), (0,0,1). Note that there
is no scaling in this transformation.
*/
CoordinateFrame localFrame() const;
void getLocalFrame(CoordinateFrame& frame) const;
/**
Returns the centroid of the box.
*/
inline Vector3 center() const
{
return _center;
}
inline Vector3 corner(int i) const
{
debugAssert(i < 8);
return _corner[i];
}
/**
Unit length.
*/
inline Vector3 axis(int a) const
{
debugAssert(a < 3);
return _axis[a];
}
/**
Distance from corner(0) to the next corner
along the box's local axis a.
*/
inline float extent(int a) const
{
debugAssert(a < 3);
return (float)_extent[a];
}
inline Vector3 extent() const
{
return _extent;
}
/**
Returns the four corners of a face (0 <= f < 6).
The corners are returned to form a counter clockwise quad facing outwards.
*/
void getFaceCorners(int f, Vector3& v0, Vector3& v1, Vector3& v2, Vector3& v3) const;
/**
See AABox::culledBy
*/
bool culledBy(const Array<Plane>& plane, int32& cullingPlaneIndex, const uint32 testMask, uint32& childMask) const;
/**
Conservative culling test that does not produce a mask for children.
*/
bool culledBy(const Array<Plane>& plane, int32& cullingPlaneIndex = dummy, const uint32 testMask = -1) const;
bool contains(const Vector3& point) const;
float area() const;
float volume() const;
void getRandomSurfacePoint(Vector3& P, Vector3& N = Vector3::ignore()) const;
/**
Uniformly distributed on the interior (includes surface)
*/
Vector3 randomInteriorPoint() const;
void getBounds(class AABox&) const;
bool isFinite() const
{
return G3D::isFinite(_volume);
}
};
} // namespace G3D
#endif

View File

@@ -0,0 +1,60 @@
/**
\file BumpMapPreprocess.h
\maintainer Morgan McGuire, http://graphics.cs.williams.edu
\created 2010-01-28
\edited 2010-01-28
Copyright 2000-2010, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_BumpMapPreprocess_h
#define G3D_BumpMapPreprocess_h
#include "platform.hpp"
namespace G3D
{
/**
Not in the BumpMap class to avoid a circular dependency between Texture and BumpMap.
G3D::GImage::computeNormalMap().
*/
class BumpMapPreprocess
{
public:
/** If true, the elevations are box filtered after computing normals
and before uploading, which produces better results for parallax offset mapping
Defaults to false. */
bool lowPassFilter;
/** Height of the maximum ("white") value, in pixels, for the purpose of computing normals.
A value of 255 means that a 255 x 255 bump image with a full black-to-white gradient
will produce a 45-degree ramp (this also results in "cubic" voxels).
A negative value means to set zExtentPixels to -zExtentPixels * max(width, height).
The default is -0.02.
*/
float zExtentPixels;
/** After computing normals, scale the height by |N.z|, a trick that reduces texture swim in steep areas for parallax offset
mapping. Defaults to false.*/
bool scaleZByNz;
BumpMapPreprocess()
: lowPassFilter(false)
, zExtentPixels(-0.02f)
, scaleZByNz(false)
{
}
bool operator==(const BumpMapPreprocess& other) const
{
return (lowPassFilter == other.lowPassFilter) && (zExtentPixels == other.zExtentPixels) && (scaleZByNz == other.scaleZByNz);
}
};
} // namespace G3D
#endif

172
engine/3d/src/Capsule.cpp Normal file
View File

@@ -0,0 +1,172 @@
/**
@file Capsule.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-02-07
@edited 2005-08-18
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#include "Capsule.hpp"
#include "LineSegment.hpp"
#include "Sphere.hpp"
#include "CoordinateFrame.hpp"
#include "Line.hpp"
#include "AABox.hpp"
namespace G3D
{
Capsule::Capsule() {}
Capsule::Capsule(const Vector3& _p1, const Vector3& _p2, float _r)
: p1(_p1)
, p2(_p2)
, _radius(_r)
{
}
Line Capsule::axis() const
{
return Line::fromTwoPoints(p1, p2);
}
float Capsule::volume() const
{
return
// Sphere volume
pow(_radius, 3) * pi() * 4 / 3 +
// Cylinder volume
pow(_radius, 2) * (p1 - p2).magnitude();
}
float Capsule::area() const
{
return
// Sphere area
pow(_radius, 2) * 4 * pi() +
// Cylinder area
twoPi() * _radius * (p1 - p2).magnitude();
}
void Capsule::getBounds(AABox& out) const
{
Vector3 min = p1.min(p2) - (Vector3(1, 1, 1) * _radius);
Vector3 max = p1.max(p2) + (Vector3(1, 1, 1) * _radius);
out = AABox(min, max);
}
bool Capsule::contains(const Vector3& p) const
{
return LineSegment::fromTwoPoints(p1, p2).distanceSquared(p) <= square(radius());
}
void Capsule::getRandomSurfacePoint(Vector3& p, Vector3& N) const
{
float h = height();
float r = radius();
// Create a random point on a standard capsule and then rotate to the global frame.
// Relative areas
float capRelArea = sqrt(r) / 2.0f;
float sideRelArea = r * h;
float r1 = uniformRandom(0, capRelArea * 2 + sideRelArea);
if (r1 < capRelArea * 2)
{
// Select a point uniformly at random on a sphere
N = Sphere(Vector3::zero(), 1).randomSurfacePoint();
p = N * r;
p.y += sign(p.y) * h / 2.0f;
}
else
{
// Side
float a = uniformRandom(0, (float)twoPi());
N.x = cos(a);
N.y = 0;
N.z = sin(a);
p.x = N.x * r;
p.z = N.y * r;
p.y = uniformRandom(-h / 2.0f, h / 2.0f);
}
// Transform to world space
CoordinateFrame cframe;
getReferenceFrame(cframe);
p = cframe.pointToWorldSpace(p);
N = cframe.normalToWorldSpace(N);
}
void Capsule::getReferenceFrame(CoordinateFrame& cframe) const
{
cframe.translation = center();
Vector3 Y = (p1 - p2).direction();
Vector3 X = (abs(Y.dot(Vector3::unitX())) > 0.9) ? Vector3::unitY() : Vector3::unitX();
Vector3 Z = X.cross(Y).direction();
X = Y.cross(Z);
cframe.rotation.setColumn(0, X);
cframe.rotation.setColumn(1, Y);
cframe.rotation.setColumn(2, Z);
}
Vector3 Capsule::randomInteriorPoint() const
{
float h = height();
float r = radius();
// Create a random point in a standard capsule and then rotate to the global frame.
Vector3 p;
float hemiVolume = pi() * (r * r * r) * 4 / 6.0;
float cylVolume = pi() * square(r) * h;
float r1 = uniformRandom(0, 2.0 * hemiVolume + cylVolume);
if (r1 < 2.0 * hemiVolume)
{
p = Sphere(Vector3::zero(), r).randomInteriorPoint();
p.y += sign(p.y) * h / 2.0f;
}
else
{
// Select a point uniformly at random on a disk
float a = uniformRandom(0, (float)twoPi());
float r2 = sqrt(uniformRandom(0, 1)) * r;
p = Vector3(cos(a) * r2, uniformRandom(-h / 2.0f, h / 2.0f), sin(a) * r2);
}
// Transform to world space
CoordinateFrame cframe;
getReferenceFrame(cframe);
return cframe.pointToWorldSpace(p);
}
} // namespace G3D

93
engine/3d/src/Capsule.hpp Normal file
View File

@@ -0,0 +1,93 @@
/**
@file Capsule.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-02-07
@edited 2005-08-20
Copyright 2000-2006, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_CAPSULE_H
#define G3D_CAPSULE_H
#include "platform.hpp"
#include "g3dmath.hpp"
#include "Vector3.hpp"
namespace G3D
{
class Line;
class AABox;
/**
A shape formed by extruding a sphere along a line segment.
*/
class Capsule
{
private:
Vector3 p1;
Vector3 p2;
float _radius;
public:
/** Uninitialized */
Capsule();
Capsule(const Vector3& _p1, const Vector3& _p2, float _r);
/** The line down the center of the capsule */
Line axis() const;
inline float radius() const
{
return _radius;
}
/** Argument may be 0 or 1 */
inline Vector3 point(int i) const
{
debugAssert(i == 0 || i == 1);
return (i == 0) ? p1 : p2;
}
/** Distance between the sphere centers. The total extent of the cylinder is
2r + h. */
inline float height() const
{
return (p1 - p2).magnitude();
}
inline Vector3 center() const
{
return (p1 + p2) / 2.0;
}
/** Get a reference frame in which the center of mass is the origin and Y is the axis of the capsule.*/
void getReferenceFrame(class CoordinateFrame& cframe) const;
/**
Returns true if the point is inside the capsule or on its surface.
*/
bool contains(const Vector3& p) const;
float volume() const;
float area() const;
/** Get axis aligned bounding box */
void getBounds(AABox& out) const;
/** Random world space point with outward facing normal. */
void getRandomSurfacePoint(Vector3& P, Vector3& N) const;
/** Point selected uniformly at random over the volume. */
Vector3 randomInteriorPoint() const;
};
} // namespace G3D
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

164
engine/3d/src/Color1.hpp Normal file
View File

@@ -0,0 +1,164 @@
/**
@file Color1.h
Monochrome Color class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2007-01-31
@edited 2009-03-20
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_COLOR1_H
#define G3D_COLOR1_H
#include "platform.hpp"
#include "g3dmath.hpp"
#include "HashTrait.hpp"
#include <string>
namespace G3D
{
/**
Monochrome color. This is just a float, but it has nice semantics
because a scaling by 255 automatically occurs when switching between
fixed point (Color1uint8) and floating point (Color1) formats.
*/
class Color1
{
private:
// Hidden operators
bool operator<(const Color1&) const;
bool operator>(const Color1&) const;
bool operator<=(const Color1&) const;
bool operator>=(const Color1&) const;
public:
float value;
/**
Initializes to 0
*/
inline Color1()
: value(0)
{
}
inline explicit Color1(float v)
: value(v)
{
}
inline bool isZero() const
{
return value == 0.0f;
}
inline bool isOne() const
{
return value == 1.0f;
}
static const Color1& one();
static const Color1& zero();
/** Returns the value three times */
class Color3 rgb() const;
Color1(const class Color1uint8& other);
Color1 operator+(const Color1& other) const
{
return Color1(value + other.value);
}
Color1 operator+(const float other) const
{
return Color1(value + other);
}
Color1& operator+=(const Color1 other)
{
value += other.value;
return *this;
}
Color1& operator-=(const Color1 other)
{
value -= other.value;
return *this;
}
Color1 operator-(const Color1& other) const
{
return Color1(value - other.value);
}
Color1 operator-(const float other) const
{
return Color1(value - other);
}
Color1 operator-() const
{
return Color1(-value);
}
Color1 operator*(const Color1& other) const
{
return Color1(value * other.value);
}
Color1 operator*(const float other) const
{
return Color1(value * other);
}
Color1 operator/(const Color1& other) const
{
return Color1(value / other.value);
}
Color1 operator/(const float other) const
{
return Color1(value / other);
}
inline Color1 max(const Color1& other) const
{
return Color1(G3D::max(value, other.value));
}
inline Color1 min(const Color1& other) const
{
return Color1(G3D::min(value, other.value));
}
inline Color1 lerp(const Color1& other, float a) const
{
return Color1(value + (other.value - value) * a);
}
inline size_t hashCode() const
{
return (size_t)(value * 0xFFFFFF);
}
};
} // namespace G3D
template<>
struct HashTrait<G3D::Color1>
{
static size_t hashCode(const G3D::Color1& key)
{
return key.hashCode();
}
};
#endif

View File

@@ -0,0 +1,94 @@
/**
@file Color1uint8.h
@maintainer Morgan McGuire, graphics3d.com
@created 2007-01-30
@edited 2007-01-30
Copyright 2000-2007, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_COLOR1UINT8_H
#define G3D_COLOR1UINT8_H
#include "platform.hpp"
#include "g3dmath.hpp"
namespace G3D
{
/**
Represents a Color1 as a packed integer. Convenient
for creating unsigned int vertex arrays.
<B>WARNING</B>: Integer color formats are different than
integer vertex formats. The color channels are automatically
scaled by 255 (because OpenGL automatically scales integer
colors back by this factor). So Color3(1,1,1) == Color3uint8(255,255,255)
but Vector3(1,1,1) == Vector3int16(1,1,1).
<B>Note</B>:
Conversion of a float32 to uint8 is accomplished by min(iFloor(f * 256)) and
back to float32 by u / 255.0f. This gives equal size intervals.
Consider a number line from 0 to 1 and a corresponding one from 0 to 255. If we use iRound(x * 255), then the mapping for three critical intervals
are:
<pre>
let s = 0.5/255
float int size
[0, s) -> 0 s
[s, s * 3) -> 1 2*s
(1 - s, 1] -> 255 s
</pre>
If we use max(floor(x * 256), 255), then we get:
<pre>
let s = 1/256
float int size
[0, s) -> 0 s
[s, 2 * s) -> 1 s
(1 - s, 1] -> 255 s
</PRE>
and the intervals are all the same size, thus giving equal precision to all values.
*/
G3D_BEGIN_PACKED_CLASS(1)
class Color1uint8
{
private:
// Hidden operators
bool operator<(const Color1uint8&) const;
bool operator>(const Color1uint8&) const;
bool operator<=(const Color1uint8&) const;
bool operator>=(const Color1uint8&) const;
public:
uint8 value;
Color1uint8()
: value(0)
{
}
explicit Color1uint8(const uint8 _v)
: value(_v)
{
}
Color1uint8(const class Color1& c);
inline bool operator==(const Color1uint8& other) const
{
return value == other.value;
}
inline bool operator!=(const Color1uint8& other) const
{
return value != other.value;
}
} G3D_END_PACKED_CLASS(1)
} // namespace G3D
#endif

345
engine/3d/src/Color3.cpp Normal file
View File

@@ -0,0 +1,345 @@
/**
@file Color3.cpp
Color class.
@author Morgan McGuire, http://graphics.cs.williams.edu
@created 2001-06-02
@edited 2010-01-28
*/
#include "platform.hpp"
#include <stdlib.h>
#include "Color3.hpp"
#include "Vector3.hpp"
#include "format.hpp"
#include "Color3uint8.hpp"
#include "stringutils.hpp"
namespace G3D
{
Color3 Color3::ansiMap(uint32 i)
{
static const Color3 map[] = {Color3::black(), Color3::red() * 0.75f, Color3::green() * 0.75f, Color3::yellow() * 0.75f, Color3::blue() * 0.75f,
Color3::purple() * 0.75f, Color3::cyan() * 0.75f, Color3::white() * 0.75f, Color3::white() * 0.90f, Color3::red(), Color3::green(),
Color3::yellow(), Color3::blue(), Color3::purple(), Color3::cyan(), Color3::white()};
return map[i & 15];
}
Color3 Color3::pastelMap(uint32 i)
{
uint32 x = Crypto::crc32(&i, sizeof(uint32));
// Create fairly bright, saturated colors
Vector3 v(((x >> 22) & 1023) / 1023.0f, (((x >> 11) & 2047) / 2047.0f) * 0.5f + 0.25f, ((x & 2047) / 2047.0f) * 0.75f + 0.25f);
return Color3::fromHSV(v);
}
const Color3& Color3::red()
{
static Color3 c(1.0f, 0.0f, 0.0f);
return c;
}
const Color3& Color3::green()
{
static Color3 c(0.0f, 1.0f, 0.0f);
return c;
}
const Color3& Color3::blue()
{
static Color3 c(0.0f, 0.0f, 1.0f);
return c;
}
const Color3& Color3::purple()
{
static Color3 c(0.7f, 0.0f, 1.0f);
return c;
}
const Color3& Color3::cyan()
{
static Color3 c(0.0f, 0.7f, 1.0f);
return c;
}
const Color3& Color3::yellow()
{
static Color3 c(1.0f, 1.0f, 0.0f);
return c;
}
const Color3& Color3::brown()
{
static Color3 c(0.5f, 0.5f, 0.0f);
return c;
}
const Color3& Color3::orange()
{
static Color3 c(1.0f, 0.5f, 0.0f);
return c;
}
const Color3& Color3::black()
{
static Color3 c(0.0f, 0.0f, 0.0f);
return c;
}
const Color3& Color3::zero()
{
static Color3 c(0.0f, 0.0f, 0.0f);
return c;
}
const Color3& Color3::one()
{
static Color3 c(1.0f, 1.0f, 1.0f);
return c;
}
const Color3& Color3::gray()
{
static Color3 c(0.7f, 0.7f, 0.7f);
return c;
}
const Color3& Color3::white()
{
static Color3 c(1, 1, 1);
return c;
}
bool Color3::isFinite() const
{
return G3D::isFinite(r) && G3D::isFinite(g) && G3D::isFinite(b);
}
const Color3& Color3::wheelRandom()
{
static const Color3 colorArray[8] = {
Color3::blue(), Color3::red(), Color3::green(), Color3::orange(), Color3::yellow(), Color3::cyan(), Color3::purple(), Color3::brown()};
return colorArray[iRandom(0, 7)];
}
size_t Color3::hashCode() const
{
unsigned int rhash = (*(int*)(void*)(&r));
unsigned int ghash = (*(int*)(void*)(&g));
unsigned int bhash = (*(int*)(void*)(&b));
return rhash + (ghash * 37) + (bhash * 101);
}
Color3::Color3(const Vector3& v)
{
r = v.x;
g = v.y;
b = v.z;
}
Color3::Color3(const class Color3uint8& other)
{
r = other.r / 255.0f;
g = other.g / 255.0f;
b = other.b / 255.0f;
}
Color3 Color3::fromARGB(uint32 x)
{
return Color3((float)((x >> 16) & 0xFF), (float)((x >> 8) & 0xFF), (float)(x & 0xFF)) / 255.0f;
}
//----------------------------------------------------------------------------
Color3 Color3::random()
{
return Color3(uniformRandom(), uniformRandom(), uniformRandom()).direction();
}
//----------------------------------------------------------------------------
Color3& Color3::operator/=(float fScalar)
{
if (fScalar != 0.0f)
{
float fInvScalar = 1.0f / fScalar;
r *= fInvScalar;
g *= fInvScalar;
b *= fInvScalar;
}
else
{
r = (float)G3D::finf();
g = (float)G3D::finf();
b = (float)G3D::finf();
}
return *this;
}
//----------------------------------------------------------------------------
float Color3::unitize(float fTolerance)
{
float fLength = length();
if (fLength > fTolerance)
{
float fInvLength = 1.0f / fLength;
r *= fInvLength;
g *= fInvLength;
b *= fInvLength;
}
else
{
fLength = 0.0f;
}
return fLength;
}
//----------------------------------------------------------------------------
Color3 Color3::fromHSV(const Vector3& _hsv)
{
debugAssertM((_hsv.x <= 1.0f && _hsv.x >= 0.0f) && (_hsv.y <= 1.0f && _hsv.y >= 0.0f) && (_hsv.z <= 1.0f && _hsv.z >= 0.0f),
"H,S,V must be between [0,1]");
const int i = iMin(5, G3D::iFloor(6.0 * _hsv.x));
const float f = 6.0f * _hsv.x - i;
const float m = _hsv.z * (1.0f - (_hsv.y));
const float n = _hsv.z * (1.0f - (_hsv.y * f));
const float k = _hsv.z * (1.0f - (_hsv.y * (1 - f)));
switch (i)
{
case 0:
return Color3(_hsv.z, k, m);
case 1:
return Color3(n, _hsv.z, m);
case 2:
return Color3(m, _hsv.z, k);
case 3:
return Color3(m, n, _hsv.z);
case 4:
return Color3(k, m, _hsv.z);
case 5:
return Color3(_hsv.z, m, n);
default:
debugAssertM(false, "fell through switch..");
}
return Color3::black();
}
Vector3 Color3::toHSV(const Color3& _rgb)
{
debugAssertM((_rgb.r <= 1.0f && _rgb.r >= 0.0f) && (_rgb.g <= 1.0f && _rgb.g >= 0.0f) && (_rgb.b <= 1.0f && _rgb.b >= 0.0f),
"R,G,B must be between [0,1]");
Vector3 hsv = Vector3::zero();
hsv.z = G3D::max(G3D::max(_rgb.r, _rgb.g), _rgb.b);
if (G3D::fuzzyEq(hsv.z, 0.0f))
{
return hsv;
}
const float x = G3D::min(G3D::min(_rgb.r, _rgb.g), _rgb.b);
hsv.y = (hsv.z - x) / hsv.z;
if (G3D::fuzzyEq(hsv.y, 0.0f))
{
return hsv;
}
Vector3 rgbN;
rgbN.x = (hsv.z - _rgb.r) / (hsv.z - x);
rgbN.y = (hsv.z - _rgb.g) / (hsv.z - x);
rgbN.z = (hsv.z - _rgb.b) / (hsv.z - x);
if (_rgb.r == hsv.z)
{ // note from the max we know that it exactly equals one of the three.
hsv.x = (_rgb.g == x) ? 5.0f + rgbN.z : 1.0f - rgbN.y;
}
else if (_rgb.g == hsv.z)
{
hsv.x = (_rgb.b == x) ? 1.0f + rgbN.x : 3.0f - rgbN.z;
}
else
{
hsv.x = (_rgb.r == x) ? 3.0f + rgbN.y : 5.0f - rgbN.x;
}
hsv.x /= 6.0f;
return hsv;
}
Color3 Color3::jetColorMap(const float& val)
{
debugAssertM(val <= 1.0f && val >= 0.0f, "value should be in [0,1]");
// truncated triangles where sides have slope 4
Color3 jet;
jet.r = G3D::min(4.0f * val - 1.5f, -4.0f * val + 4.5f);
jet.g = G3D::min(4.0f * val - 0.5f, -4.0f * val + 3.5f);
jet.b = G3D::min(4.0f * val + 0.5f, -4.0f * val + 2.5f);
jet.r = G3D::clamp(jet.r, 0.0f, 1.0f);
jet.g = G3D::clamp(jet.g, 0.0f, 1.0f);
jet.b = G3D::clamp(jet.b, 0.0f, 1.0f);
return jet;
}
// Aya - Added for boost App.Unit Test
std::ostream& operator<<(std::ostream& os, const Color3& c)
{
return os << c.toString();
}
std::string Color3::toString() const
{
return G3D::format("(%g, %g, %g)", r, g, b);
}
//----------------------------------------------------------------------------
Color3 Color3::rainbowColorMap(float hue)
{
return fromHSV(Vector3(hue, 1.0f, 1.0f));
}
}; // namespace G3D

467
engine/3d/src/Color3.hpp Normal file
View File

@@ -0,0 +1,467 @@
/**
@file Color3.h
Color class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@cite Portions based on Dave Eberly's Magic Software Library
at <A HREF="http://www.magic-software.com">http://www.magic-software.com</A>
@created 2001-06-02
@edited 2009-04-28
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_Color3_h
#define G3D_Color3_h
#include "platform.hpp"
#include "g3dmath.hpp"
#include "HashTrait.hpp"
#include "Color1.hpp"
#include <string>
namespace G3D
{
/**
Do not subclass-- this implementation makes assumptions about the
memory layout.
*/
class Color3
{
private:
// Hidden operators
bool operator<(const Color3&) const;
bool operator>(const Color3&) const;
bool operator<=(const Color3&) const;
bool operator>=(const Color3&) const;
public:
/**
Does not initialize fields.
*/
Color3();
Color3(float r, float g, float b);
explicit Color3(float v)
: r(v)
, g(v)
, b(v)
{
}
explicit Color3(const class Vector3& v);
explicit Color3(const float value[3]);
/** Returns this color */
const Color3& rgb() const
{
return *this;
}
/**
Initialize from another color.
*/
Color3(const Color3& other);
Color3(const class Color3uint8& other);
inline bool isZero() const
{
return (r == 0.0f) && (g == 0.0f) && (b == 0.0f);
}
inline bool isOne() const
{
return (r == 1.0f) && (g == 1.0f) && (b == 1.0f);
}
bool isFinite() const;
/**
Initialize from an HTML-style color (e.g. 0xFF0000 == RED)
*/
static Color3 fromARGB(uint32);
/** Returns one of the color wheel colors (e.g. RED, GREEN, CYAN).
Does not include white, black, or gray. */
static const Color3& wheelRandom();
/** Generate colors according to the ANSI color set, mod 16.
\sa pastelMap */
static Color3 ansiMap(uint32 i);
/**
Generate colors using a hash such that adjacent values
are unlikely to have similar colors.
Useful for rendering with
stable but arbitrary colors, e.g., when debugging a mesh
algorithm.
\sa ansiMap
*/
static Color3 pastelMap(uint32 i);
/**
* Channel value.
*/
float r, g, b;
// access vector V as V[0] = V.r, V[1] = V.g, V[2] = V.b
//
// WARNING. These member functions rely on
// (1) Color3 not having virtual functions
// (2) the data packed in a 3*sizeof(float) memory block
const float& operator[](int i) const;
float& operator[](int i);
// assignment and comparison
Color3& operator=(const Color3& rkVector);
bool operator==(const Color3& rkVector) const;
bool operator!=(const Color3& rkVector) const;
size_t hashCode() const;
// arithmetic operations
Color3 operator+(const Color3& rkVector) const;
Color3 operator-(const Color3& rkVector) const;
inline Color3 operator*(float s) const
{
return Color3(r * s, g * s, b * s);
}
Color3 operator*(const Color3& rkVector) const;
inline Color3 operator/(float fScalar) const
{
return (*this) * (1.0f / fScalar);
}
Color3 operator-() const;
// arithmetic updates
Color3& operator+=(const Color3& rkVector);
Color3& operator-=(const Color3& rkVector);
Color3& operator*=(const Color3& rkVector);
Color3& operator*=(float fScalar);
Color3& operator/=(float fScalar);
bool fuzzyEq(const Color3& other) const;
bool fuzzyNe(const Color3& other) const;
// vector operations
float length() const;
Color3 direction() const;
float squaredLength() const;
float dot(const Color3& rkVector) const;
float unitize(float fTolerance = 1e-06);
Color3 cross(const Color3& rkVector) const;
Color3 unitCross(const Color3& rkVector) const;
inline Color3 pow(const Color3& other) const
{
return Color3(::pow(r, other.r), ::pow(g, other.g), ::pow(b, other.b));
}
inline Color3 pow(float other) const
{
return Color3(::pow(r, other), ::pow(g, other), ::pow(b, other));
}
inline Color3 max(const Color3& other) const
{
return Color3(G3D::max(r, other.r), G3D::max(g, other.g), G3D::max(b, other.b));
}
inline Color3 min(const Color3& other) const
{
return Color3(G3D::min(r, other.r), G3D::min(g, other.g), G3D::min(b, other.b));
}
/** Smallest element */
inline float min() const
{
return G3D::min(G3D::min(r, g), b);
}
/** Largest element */
inline float max() const
{
return G3D::max(G3D::max(r, g), b);
}
inline Color3 lerp(const Color3& other, float a) const
{
return (*this) + (other - *this) * a;
}
inline float sum() const
{
return r + g + b;
}
inline float average() const
{
return sum() / 3.0f;
}
/**
* Converts from HSV to RGB , note: toHSV(fromHSV(_hsv)) may not be _hsv, if it is at a grey point or black point.
* The components of _hsv should lie in the unit interval.
* @cite Alvy Ray Smith SIGGRAPH 1978 "Color Gamut Transform Pairs"
**/
static Color3 fromHSV(const Vector3& _hsv);
static Vector3 toHSV(const Color3& _rgb);
/** Duplicates the matlab jet colormap maps [0,1] --> (r,g,b) where blue is close to 0 and red is close to 1. */
static Color3 jetColorMap(const float& val);
/** Returns colors with maximum saturation and value @param hue [0, 1]*/
static Color3 rainbowColorMap(float hue);
std::string toString() const;
/** Random unit vector */
static Color3 random();
// Special values.
// Intentionally not inlined: see Matrix3::identity() for details.
static const Color3& red();
static const Color3& green();
static const Color3& blue();
static const Color3& purple();
static const Color3& cyan();
static const Color3& yellow();
static const Color3& brown();
static const Color3& orange();
static const Color3& black();
static const Color3& gray();
static const Color3& white();
static const Color3& zero();
static const Color3& one();
inline Color3 bgr() const
{
return Color3(b, g, r);
}
};
inline G3D::Color3 operator*(float s, const G3D::Color3& c)
{
return c * s;
}
inline G3D::Color3 operator*(G3D::Color1& s, const G3D::Color3& c)
{
return c * s.value;
}
inline G3D::Color3 operator*(const G3D::Color3& c, G3D::Color1& s)
{
return c * s.value;
}
std::ostream& operator<<(std::ostream& os, const Color3&); // Aya - Added for boost App.Unit Test
//----------------------------------------------------------------------------
inline Color3::Color3() {}
//----------------------------------------------------------------------------
inline Color3::Color3(float fX, float fY, float fZ)
{
r = fX;
g = fY;
b = fZ;
}
//----------------------------------------------------------------------------
inline Color3::Color3(const float afCoordinate[3])
{
r = afCoordinate[0];
g = afCoordinate[1];
b = afCoordinate[2];
}
//----------------------------------------------------------------------------
inline Color3::Color3(const Color3& rkVector)
{
r = rkVector.r;
g = rkVector.g;
b = rkVector.b;
}
//----------------------------------------------------------------------------
inline float& Color3::operator[](int i)
{
return ((float*)this)[i];
}
//----------------------------------------------------------------------------
inline const float& Color3::operator[](int i) const
{
return ((float*)this)[i];
}
//----------------------------------------------------------------------------
inline bool Color3::fuzzyEq(const Color3& other) const
{
return G3D::fuzzyEq((*this - other).squaredLength(), 0);
}
//----------------------------------------------------------------------------
inline bool Color3::fuzzyNe(const Color3& other) const
{
return G3D::fuzzyNe((*this - other).squaredLength(), 0);
}
//----------------------------------------------------------------------------
inline Color3& Color3::operator=(const Color3& rkVector)
{
r = rkVector.r;
g = rkVector.g;
b = rkVector.b;
return *this;
}
//----------------------------------------------------------------------------
inline bool Color3::operator==(const Color3& rkVector) const
{
return (r == rkVector.r && g == rkVector.g && b == rkVector.b);
}
//----------------------------------------------------------------------------
inline bool Color3::operator!=(const Color3& rkVector) const
{
return (r != rkVector.r || g != rkVector.g || b != rkVector.b);
}
//----------------------------------------------------------------------------
inline Color3 Color3::operator+(const Color3& rkVector) const
{
return Color3(r + rkVector.r, g + rkVector.g, b + rkVector.b);
}
//----------------------------------------------------------------------------
inline Color3 Color3::operator-(const Color3& rkVector) const
{
return Color3(r - rkVector.r, g - rkVector.g, b - rkVector.b);
}
//----------------------------------------------------------------------------
inline Color3 Color3::operator*(const Color3& rkVector) const
{
return Color3(r * rkVector.r, g * rkVector.g, b * rkVector.b);
}
//----------------------------------------------------------------------------
inline Color3 Color3::operator-() const
{
return Color3(-r, -g, -b);
}
//----------------------------------------------------------------------------
inline Color3& Color3::operator+=(const Color3& rkVector)
{
r += rkVector.r;
g += rkVector.g;
b += rkVector.b;
return *this;
}
//----------------------------------------------------------------------------
inline Color3& Color3::operator-=(const Color3& rkVector)
{
r -= rkVector.r;
g -= rkVector.g;
b -= rkVector.b;
return *this;
}
//----------------------------------------------------------------------------
inline Color3& Color3::operator*=(float fScalar)
{
r *= fScalar;
g *= fScalar;
b *= fScalar;
return *this;
}
//----------------------------------------------------------------------------
inline Color3& Color3::operator*=(const Color3& rkVector)
{
r *= rkVector.r;
g *= rkVector.g;
b *= rkVector.b;
return *this;
}
//----------------------------------------------------------------------------
inline float Color3::squaredLength() const
{
return r * r + g * g + b * b;
}
//----------------------------------------------------------------------------
inline float Color3::length() const
{
return sqrtf(r * r + g * g + b * b);
}
//----------------------------------------------------------------------------
inline Color3 Color3::direction() const
{
float lenSquared = r * r + g * g + b * b;
if (lenSquared != 1.0f)
{
return *this / sqrtf(lenSquared);
}
else
{
return *this;
}
}
//----------------------------------------------------------------------------
inline float Color3::dot(const Color3& rkVector) const
{
return r * rkVector.r + g * rkVector.g + b * rkVector.b;
}
//----------------------------------------------------------------------------
inline Color3 Color3::cross(const Color3& rkVector) const
{
return Color3(g * rkVector.b - b * rkVector.g, b * rkVector.r - r * rkVector.b, r * rkVector.g - g * rkVector.r);
}
//----------------------------------------------------------------------------
inline Color3 Color3::unitCross(const Color3& rkVector) const
{
Color3 kCross(g * rkVector.b - b * rkVector.g, b * rkVector.r - r * rkVector.b, r * rkVector.g - g * rkVector.r);
kCross.unitize();
return kCross;
}
} // namespace G3D
template<>
struct HashTrait<G3D::Color3>
{
static size_t hashCode(const G3D::Color3& key)
{
return key.hashCode();
}
};
#endif

View File

@@ -0,0 +1,26 @@
/**
@file Color3uint8.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-04-07
@edited 2006-01-07
*/
#include "platform.hpp"
#include "g3dmath.hpp"
#include "Color3uint8.hpp"
#include "Color3.hpp"
namespace G3D
{
Color3uint8::Color3uint8(const class Color3& c)
{
r = iMin(255, iFloor(c.r * 256));
g = iMin(255, iFloor(c.g * 256));
b = iMin(255, iFloor(c.b * 256));
}
} // namespace G3D

View File

@@ -0,0 +1,140 @@
/**
@file Color3uint8.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-04-07
@edited 2010-03-24
Copyright 2000-2010, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_Color3uint8_h
#define G3D_Color3uint8_h
#include "platform.hpp"
#include "g3dmath.hpp"
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
namespace G3D
{
/**
Represents a Color3 as a packed integer. Convenient
for creating unsigned int vertex arrays. Used by
G3D::GImage as the underlying format.
<B>WARNING</B>: Integer color formats are different than
integer vertex formats. The color channels are automatically
scaled by 255 (because OpenGL automatically scales integer
colors back by this factor). So Color3(1,1,1) == Color3uint8(255,255,255)
but Vector3(1,1,1) == Vector3int16(1,1,1).
*/
G3D_BEGIN_PACKED_CLASS(1)
class Color3uint8
{
private:
// Hidden operators
bool operator<(const Color3uint8&) const;
bool operator>(const Color3uint8&) const;
bool operator<=(const Color3uint8&) const;
bool operator>=(const Color3uint8&) const;
public:
uint8 r;
uint8 g;
uint8 b;
Color3uint8()
: r(0)
, g(0)
, b(0)
{
}
Color3uint8(const uint8 _r, const uint8 _g, const uint8 _b)
: r(_r)
, g(_g)
, b(_b)
{
}
Color3uint8(const class Color3& c);
static Color3uint8 fromARGB(uint32 i)
{
Color3uint8 c;
c.r = (i >> 16) & 0xFF;
c.g = (i >> 8) & 0xFF;
c.b = i & 0xFF;
return c;
}
Color3uint8 bgr() const
{
return Color3uint8(b, g, r);
}
Color3uint8 max(const Color3uint8 x) const
{
return Color3uint8(G3D::max(r, x.r), G3D::max(g, x.g), G3D::max(b, x.b));
}
Color3uint8 min(const Color3uint8 x) const
{
return Color3uint8(G3D::min(r, x.r), G3D::min(g, x.g), G3D::min(b, x.b));
}
/**
Returns the color packed into a uint32
(the upper byte is 0xFF)
*/
uint32 asUInt32() const
{
return (0xFF << 24) + ((uint32)r << 16) + ((uint32)g << 8) + b;
}
// access vector V as V[0] = V.r, V[1] = V.g, V[2] = V.b
//
// WARNING. These member functions rely on
// (1) Color3 not having virtual functions
// (2) the data packed in a 3*sizeof(uint8) memory block
uint8& operator[](int i) const
{
debugAssert((unsigned int)i < 3);
return ((uint8*)this)[i];
}
operator uint8*()
{
return (G3D::uint8*)this;
}
operator const uint8*() const
{
return (uint8*)this;
}
bool operator==(const Color3uint8 other) const
{
return (other.r == r) && (other.g == g) && (other.b == b);
}
bool operator!=(const Color3uint8 other) const
{
return !(*this == other);
}
} G3D_END_PACKED_CLASS(1)
} // namespace G3D
#endif

147
engine/3d/src/Color4.cpp Normal file
View File

@@ -0,0 +1,147 @@
/**
@file Color4.cpp
Color class.
@author Morgan McGuire, http://graphics.cs.williams.edu
@cite Portions by Laura Wollstadt, graphics3d.com
@cite Portions based on Dave Eberly's Magic Software Library at http://www.magic-software.com
@created 2002-06-25
@edited 2009-11-10
*/
#include <stdlib.h>
#include "Color4.hpp"
#include "Color4uint8.hpp"
#include "Vector4.hpp"
#include "format.hpp"
#include "stringutils.hpp"
namespace G3D
{
const Color4& Color4::one()
{
const static Color4 x(1.0f, 1.0f, 1.0f, 1.0f);
return x;
}
const Color4& Color4::zero()
{
static Color4 c(0.0f, 0.0f, 0.0f, 0.0f);
return c;
}
const Color4& Color4::inf()
{
static Color4 c((float)G3D::finf(), (float)G3D::finf(), (float)G3D::finf(), (float)G3D::finf());
return c;
}
const Color4& Color4::nan()
{
static Color4 c((float)G3D::fnan(), (float)G3D::fnan(), (float)G3D::fnan(), (float)G3D::fnan());
return c;
}
const Color4& Color4::clear()
{
return Color4::zero();
}
Color4::Color4(const Vector4& v)
{
r = v.x;
g = v.y;
b = v.z;
a = v.w;
}
Color4::Color4(const Color4uint8& c)
: r(c.r)
, g(c.g)
, b(c.b)
, a(c.a)
{
*this /= 255.0f;
}
size_t Color4::hashCode() const
{
unsigned int rhash = (*(int*)(void*)(&r));
unsigned int ghash = (*(int*)(void*)(&g));
unsigned int bhash = (*(int*)(void*)(&b));
unsigned int ahash = (*(int*)(void*)(&a));
return rhash + (ghash * 37) + (bhash * 101) + (ahash * 241);
}
Color4 Color4::fromARGB(uint32 x)
{
return Color4((float)((x >> 16) & 0xFF), (float)((x >> 8) & 0xFF), (float)(x & 0xFF), (float)((x >> 24) & 0xFF)) / 255.0;
}
//----------------------------------------------------------------------------
Color4 Color4::operator/(float fScalar) const
{
Color4 kQuot;
if (fScalar != 0.0f)
{
float fInvScalar = 1.0f / fScalar;
kQuot.r = fInvScalar * r;
kQuot.g = fInvScalar * g;
kQuot.b = fInvScalar * b;
kQuot.a = fInvScalar * a;
return kQuot;
}
else
{
return Color4::inf();
}
}
//----------------------------------------------------------------------------
Color4& Color4::operator/=(float fScalar)
{
if (fScalar != 0.0f)
{
float fInvScalar = 1.0f / fScalar;
r *= fInvScalar;
g *= fInvScalar;
b *= fInvScalar;
a *= fInvScalar;
}
else
{
r = (float)G3D::finf();
g = (float)G3D::finf();
b = (float)G3D::finf();
a = (float)G3D::finf();
}
return *this;
}
//----------------------------------------------------------------------------
std::string Color4::toString() const
{
return G3D::format("(%g, %g, %g, %g)", r, g, b, a);
}
//----------------------------------------------------------------------------
}; // namespace G3D

365
engine/3d/src/Color4.hpp Normal file
View File

@@ -0,0 +1,365 @@
/**
@file Color4.h
Color class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@cite Portions based on Dave Eberly's Magic Software Library
at <A HREF="http://www.magic-software.com">http://www.magic-software.com</A>
@created 2002-06-25
@edited 2009-11-15
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_Color4_h
#define G3D_Color4_h
#include "platform.hpp"
#include "g3dmath.hpp"
#include "Color3.hpp"
#include <string>
namespace G3D
{
/**
Do not subclass-- this implementation makes assumptions about the
memory layout.
*/
class Color4
{
private:
// Hidden operators
bool operator<(const Color4&) const;
bool operator>(const Color4&) const;
bool operator<=(const Color4&) const;
bool operator>=(const Color4&) const;
public:
/** \param any Must be in one of the following forms:
- Color4(#, #, #, #)
- Color4::fromARGB(#)
- Color4{r = #, g = #, b = #, a = #)
*/
/**
* Does not initialize fields.
*/
Color4();
Color4(const Color3& c3, float a = 1.0);
Color4(const class Color4uint8& c);
Color4(const class Vector4& v);
Color4(float r, float g, float b, float a = 1.0);
static const Color4& one();
Color4(float value[4]);
/**
* Initialize from another color.
*/
Color4(const Color4& other);
inline bool isZero() const
{
return (r == 0.0f) && (g == 0.0f) && (b == 0.0f) && (a == 0.0f);
}
inline bool isOne() const
{
return (r == 1.0f) && (g == 1.0f) && (b == 1.0f) && (a == 1.0f);
}
/**
Initialize from an HTML-style color (e.g. 0xFFFF0000 == RED)
*/
static Color4 fromARGB(uint32);
/**
* Channel values.
*/
float r, g, b, a;
inline Color3 rgb() const
{
return Color3(r, g, b);
}
// access vector V as V[0] = V.r, V[1] = V.g, V[2] = V.b, v[3] = V.a
//
// WARNING. These member functions rely on
// (1) Color4 not having virtual functions
// (2) the data packed in a 3*sizeof(float) memory block
float& operator[](int i) const;
// assignment and comparison
Color4& operator=(const Color4& rkVector);
bool operator==(const Color4& rkVector) const;
bool operator!=(const Color4& rkVector) const;
size_t hashCode() const;
// arithmetic operations
Color4 operator+(const Color4& rkVector) const;
Color4 operator-(const Color4& rkVector) const;
Color4 operator*(float fScalar) const;
Color4 operator*(const Color4& k) const
{
return Color4(r * k.r, g * k.g, b * k.b, a * k.a);
}
Color4& operator*=(const Color4& c)
{
r *= c.r;
g *= c.g;
b *= c.b;
a *= c.a;
return *this;
}
Color4 operator/(float fScalar) const;
Color4 operator-() const;
friend Color4 operator*(double fScalar, const Color4& rkVector);
// arithmetic updates
Color4& operator+=(const Color4& rkVector);
Color4& operator-=(const Color4& rkVector);
Color4& operator*=(float fScalar);
Color4& operator/=(float fScalar);
bool fuzzyEq(const Color4& other) const;
bool fuzzyNe(const Color4& other) const;
std::string toString() const;
inline Color4 max(const Color4& other) const
{
return Color4(G3D::max(r, other.r), G3D::max(g, other.g), G3D::max(b, other.b), G3D::max(a, other.a));
}
inline Color4 min(const Color4& other) const
{
return Color4(G3D::min(r, other.r), G3D::min(g, other.g), G3D::min(b, other.b), G3D::min(a, other.a));
}
/** r + g + b + a */
inline float sum() const
{
return r + g + b + a;
}
inline Color4 lerp(const Color4& other, float a) const
{
return (*this) + (other - *this) * a;
}
// Special values.
// Intentionally not inlined: see Matrix3::identity() for details.
static const Color4& zero();
static const Color4& clear();
static const Color4& inf();
static const Color4& nan();
inline bool isFinite() const
{
return G3D::isFinite(r) && G3D::isFinite(g) && G3D::isFinite(b) && G3D::isFinite(a);
}
inline Color3 bgr() const
{
return Color3(b, g, r);
}
};
/**
Extends the c3 with alpha = 1.0
*/
Color4 operator*(const Color3& c3, const Color4& c4);
inline Color4 operator*(const Color3& c3, const Color4& c4)
{
return Color4(c3.r * c4.r, c3.g * c4.g, c3.b * c4.b, c4.a);
}
//----------------------------------------------------------------------------
inline Color4::Color4()
{
// For efficiency in construction of large arrays of vectors, the
// default constructor does not initialize the vector.
}
//----------------------------------------------------------------------------
inline Color4::Color4(const Color3& c3, float a)
{
r = c3.r;
g = c3.g;
b = c3.b;
this->a = a;
}
//----------------------------------------------------------------------------
inline Color4::Color4(float r, float g, float b, float a)
: r(r)
, g(g)
, b(b)
, a(a)
{
}
//----------------------------------------------------------------------------
inline Color4::Color4(float afCoordinate[4])
{
r = afCoordinate[0];
g = afCoordinate[1];
b = afCoordinate[2];
a = afCoordinate[3];
}
//----------------------------------------------------------------------------
inline Color4::Color4(const Color4& other)
{
r = other.r;
g = other.g;
b = other.b;
a = other.a;
}
//----------------------------------------------------------------------------
inline float& Color4::operator[](int i) const
{
return ((float*)this)[i];
}
//----------------------------------------------------------------------------
inline bool Color4::fuzzyEq(const Color4& other) const
{
Color4 dif = (*this - other);
return G3D::fuzzyEq(dif.r * dif.r + dif.g * dif.g + dif.b * dif.b + dif.a * dif.a, 0);
}
//----------------------------------------------------------------------------
inline bool Color4::fuzzyNe(const Color4& other) const
{
Color4 dif = (*this - other);
return G3D::fuzzyNe(dif.r * dif.r + dif.g * dif.g + dif.b * dif.b + dif.a * dif.a, 0);
}
//----------------------------------------------------------------------------
inline Color4& Color4::operator=(const Color4& other)
{
r = other.r;
g = other.g;
b = other.b;
a = other.a;
return *this;
}
//----------------------------------------------------------------------------
inline bool Color4::operator==(const Color4& other) const
{
return (r == other.r && g == other.g && b == other.b && a == other.a);
}
//----------------------------------------------------------------------------
inline bool Color4::operator!=(const Color4& other) const
{
return (r != other.r || g != other.g || b != other.b || a != other.a);
}
//----------------------------------------------------------------------------
inline Color4 Color4::operator+(const Color4& other) const
{
return Color4(r + other.r, g + other.g, b + other.b, a + other.a);
}
//----------------------------------------------------------------------------
inline Color4 Color4::operator-(const Color4& other) const
{
return Color4(r - other.r, g - other.g, b - other.b, a - other.a);
}
//----------------------------------------------------------------------------
inline Color4 Color4::operator*(float fScalar) const
{
return Color4(fScalar * r, fScalar * g, fScalar * b, fScalar * a);
}
//----------------------------------------------------------------------------
inline Color4 Color4::operator-() const
{
return Color4(-r, -g, -b, -a);
}
//----------------------------------------------------------------------------
inline Color4 operator*(float fScalar, const Color4& other)
{
return Color4(fScalar * other.r, fScalar * other.g, fScalar * other.b, fScalar * other.a);
}
//----------------------------------------------------------------------------
inline Color4& Color4::operator+=(const Color4& other)
{
r += other.r;
g += other.g;
b += other.b;
a += other.a;
return *this;
}
//----------------------------------------------------------------------------
inline Color4& Color4::operator-=(const Color4& other)
{
r -= other.r;
g -= other.g;
b -= other.b;
a -= other.a;
return *this;
}
//----------------------------------------------------------------------------
inline Color4& Color4::operator*=(float fScalar)
{
r *= fScalar;
g *= fScalar;
b *= fScalar;
a *= fScalar;
return *this;
}
} // namespace G3D
template<>
struct HashTrait<G3D::Color4>
{
static size_t hashCode(const G3D::Color4& key)
{
return key.hashCode();
}
};
#endif

View File

@@ -0,0 +1,26 @@
/**
@file Color4uint8.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-04-07
@edited 2006-01-07
*/
#include "platform.hpp"
#include "g3dmath.hpp"
#include "Color4uint8.hpp"
#include "Color4.hpp"
namespace G3D
{
Color4uint8::Color4uint8(const class Color4& c)
{
r = iMin(255, iFloor(c.r * 256));
g = iMin(255, iFloor(c.g * 256));
b = iMin(255, iFloor(c.b * 256));
a = iMin(255, iFloor(c.a * 256));
}
} // namespace G3D

View File

@@ -0,0 +1,147 @@
/**
@file Color4uint8.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-04-07
@edited 2010-03-24
Copyright 2000-2010, Morgan McGuire.
All rights reserved.
*/
#ifndef COLOR4UINT8_H
#define COLOR4UINT8_H
#include "g3dmath.hpp"
#include "platform.hpp"
#include "Color3uint8.hpp"
namespace G3D
{
/**
Represents a Color4 as a packed integer. Convenient
for creating unsigned int vertex arrays. Used by
G3D::GImage as the underlying format.
<B>WARNING</B>: Integer color formats are different than
integer vertex formats. The color channels are automatically
scaled by 255 (because OpenGL automatically scales integer
colors back by this factor). So Color4(1,1,1) == Color4uint8(255,255,255)
but Vector3(1,1,1) == Vector3int16(1,1,1).
*/
G3D_BEGIN_PACKED_CLASS(1)
class Color4uint8
{
private:
// Hidden operators
bool operator<(const Color4uint8&) const;
bool operator>(const Color4uint8&) const;
bool operator<=(const Color4uint8&) const;
bool operator>=(const Color4uint8&) const;
public:
uint8 r;
uint8 g;
uint8 b;
uint8 a;
Color4uint8()
: r(0)
, g(0)
, b(0)
, a(0)
{
}
Color4uint8(const class Color4& c);
Color4uint8 max(const Color4uint8 x) const
{
return Color4uint8(G3D::max(r, x.r), G3D::max(g, x.g), G3D::max(b, x.b), G3D::max(a, x.a));
}
Color4uint8 min(const Color4uint8 x) const
{
return Color4uint8(G3D::min(r, x.r), G3D::min(g, x.g), G3D::min(b, x.b), G3D::min(a, x.a));
}
Color4uint8(const uint8 _r, const uint8 _g, const uint8 _b, const uint8 _a)
: r(_r)
, g(_g)
, b(_b)
, a(_a)
{
}
Color4uint8(const Color3uint8& c, const uint8 _a)
: r(c.r)
, g(c.g)
, b(c.b)
, a(_a)
{
}
inline static Color4uint8 fromARGB(uint32 i)
{
Color4uint8 c;
c.a = (i >> 24) & 0xFF;
c.r = (i >> 16) & 0xFF;
c.g = (i >> 8) & 0xFF;
c.b = i & 0xFF;
return c;
}
inline uint32 asUInt32() const
{
return ((uint32)a << 24) + ((uint32)r << 16) + ((uint32)g << 8) + b;
}
// access vector V as V[0] = V.r, V[1] = V.g, V[2] = V.b
//
// WARNING. These member functions rely on
// (1) Color4uint8 not having virtual functions
// (2) the data packed in a 3*sizeof(uint8) memory block
uint8& operator[](int i) const
{
return ((uint8*)this)[i];
}
operator uint8*()
{
return (uint8*)this;
}
operator const uint8*() const
{
return (uint8*)this;
}
inline Color3uint8 bgr() const
{
return Color3uint8(b, g, r);
}
inline Color3uint8 rgb() const
{
return Color3uint8(r, g, b);
}
bool operator==(const Color4uint8& other) const
{
return *reinterpret_cast<const uint32*>(this) == *reinterpret_cast<const uint32*>(&other);
}
bool operator!=(const Color4uint8& other) const
{
return *reinterpret_cast<const uint32*>(this) != *reinterpret_cast<const uint32*>(&other);
}
} G3D_END_PACKED_CLASS(1)
} // namespace G3D
#endif

View File

@@ -0,0 +1,81 @@
#pragma once
#include "Utility/Math.hpp"
namespace Aya
{
class CompactCFrame
{
Vector3 rotationaxis;
float rotationangle; // angle is always positive.
public:
Vector3 translation;
CompactCFrame()
: rotationangle(0)
{
}
CompactCFrame(const CoordinateFrame& cframe)
: translation(cframe.translation)
{
cframe.rotation.toAxisAngle(rotationaxis, rotationangle);
AYAASSERT(!Math::hasNanOrInf(rotationaxis));
AYAASSERT(!Math::isNanInf(rotationangle));
}
CompactCFrame(const Vector3& translation, const Vector3& axisAngle)
: translation(translation)
, rotationaxis(axisAngle)
{
rotationangle = rotationaxis.unitize();
AYAASSERT(!Math::hasNanOrInf(rotationaxis));
AYAASSERT(!Math::isNanInf(rotationangle));
}
CompactCFrame(const Vector3& translation, const Vector3& axis, float angle)
: translation(translation)
{
setAxisAngle(axis, angle);
AYAASSERT(!Math::hasNanOrInf(rotationaxis));
AYAASSERT(!Math::isNanInf(rotationangle));
}
void setAxisAngle(const Vector3& axis, float angle)
{
// enforce angle > 0.
if (angle >= 0)
{
rotationaxis = axis;
rotationangle = angle;
}
else
{
rotationaxis = axis * -1.0f;
rotationangle = -angle;
}
}
CoordinateFrame getCFrame() const
{
CoordinateFrame answer(Matrix3::fromAxisAngleFast(rotationaxis, rotationangle), translation);
AYAASSERT(!Math::hasNanOrInf(answer));
return answer;
}
Vector3 getAxisAngle() const
{
return rotationaxis * rotationangle;
}
const Vector3& getAxis() const
{
return rotationaxis;
}
float getAngle() const
{
return rotationangle;
}
};
} // namespace Aya

69
engine/3d/src/Cone.hpp Normal file
View File

@@ -0,0 +1,69 @@
/**
@file Cone.h
Cone class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@cite Portions based on Dave Eberly's Magic Software Library at <A HREF="http://www.magic-software.com">http://www.magic-software.com</A>
@created 2001-06-02
@edited 2006-02-23
Copyright 2000-2006, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_CONE_H
#define G3D_CONE_H
#include "platform.hpp"
#include "g3dmath.hpp"
#include "Vector3.hpp"
namespace G3D
{
/**
An infinite cone.
*/
class Cone
{
private:
Vector3 tip;
Vector3 direction;
/** Angle from the center line to the edge. */
float angle;
public:
/**
@param angle Angle from the center line to the edge, in radians
*/
Cone(const Vector3& tip, const Vector3& direction, float angle);
/**
Forms the smallest cone that contains the box. Undefined if
the tip is inside or on the box.
*/
Cone(const Vector3& tip, const class Box& box);
virtual ~Cone() {}
/**
Returns true if the cone touches, intersects, or contains b.
If c.intersects(s) and c.intersects(Sphere(s.center, s.radius * 2)
then the sphere s is entirely within cone c.
*/
bool intersects(const class Sphere& s) const;
/**
True if v is a point inside the cone.
*/
bool contains(const class Vector3& v) const;
};
} // namespace G3D
#endif

View File

@@ -0,0 +1,188 @@
/**
@file ConvexPolyhedron.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2001-11-11
@edited 2006-04-10
Copyright 2000-2006, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_CONVEXPOLYHEDRON_H
#define G3D_CONVEXPOLYHEDRON_H
#include "platform.hpp"
#include "Vector3.hpp"
#include "Vector2.hpp"
#include "CoordinateFrame.hpp"
#include "Plane.hpp"
#include "Line.hpp"
#include "Array.hpp"
namespace G3D
{
class DirectedEdge
{
public:
Vector3 start;
Vector3 stop;
};
class ConvexPolygon
{
private:
friend class ConvexPolyhedron;
Array<Vector3> _vertex;
public:
ConvexPolygon() {}
ConvexPolygon(const Vector3& v0, const Vector3& v1, const Vector3& v2);
ConvexPolygon(const Array<Vector3>& __vertex);
virtual ~ConvexPolygon() {}
/**
Counter clockwise winding order.
*/
inline const Vector3& vertex(int i) const
{
return _vertex[i];
}
inline void setVertex(int i, const Vector3& v)
{
_vertex[i] = v;
}
/**
Zero vertices indicates an empty polygon (zero area).
*/
inline int numVertices() const
{
return _vertex.size();
}
inline void setNumVertices(int n)
{
_vertex.resize(n);
}
/**
O(n) in the number of edges
*/
bool isEmpty() const;
/**
Cuts the polygon at the plane. If the polygon is entirely above or below
the plane, one of the returned polygons will be empty.
@param above The part of the polygon above (on the side the
normal points to or in the plane) the plane
@param below The part of the polygon below the plane.
@param newEdge If a new edge was introduced, this is that edge (on the above portion; the below portion is the opposite winding.
*/
void cut(const Plane& plane, ConvexPolygon& above, ConvexPolygon& below, DirectedEdge& newEdge);
void cut(const Plane& plane, ConvexPolygon& above, ConvexPolygon& below);
/**
When a cut plane grazes a vertex in the polygon, two near-identical vertices may be created.
The closeness of these two points can cause a number of problems, such as ConvexPolygon::normal()
returning an infinite vector. It should be noted, however, that not all applications are
sensitive to near-identical vertices.
removeDuplicateVertices() detects and eliminates redundant vertices.
*/
void removeDuplicateVertices();
/**
O(n) in the number of edges
*/
float getArea() const;
inline Vector3 normal() const
{
debugAssert(_vertex.length() >= 3);
return (_vertex[1] - _vertex[0]).cross(_vertex[2] - _vertex[0]).direction();
}
/**
Returns the same polygon with inverse winding.
*/
ConvexPolygon inverse() const;
};
class ConvexPolyhedron
{
public:
/**
Zero faces indicates an empty polyhedron
*/
Array<ConvexPolygon> face;
ConvexPolyhedron() {}
ConvexPolyhedron(const Array<ConvexPolygon>& _face);
/**
O(n) in the number of edges
*/
bool isEmpty() const;
/**
O(n) in the number of edges
*/
float getVolume() const;
/**
Cuts the polyhedron at the plane. If the polyhedron is entirely above or below
the plane, one of the returned polyhedra will be empty.
@param above The part of the polyhedron above (on the side the
normal points to or in the plane) the plane
@param below The part of the polyhedron below the plane.
*/
void cut(const Plane& plane, ConvexPolyhedron& above, ConvexPolyhedron& below);
};
/**
*/
class ConvexPolygon2D
{
private:
Array<Vector2> m_vertex;
public:
ConvexPolygon2D() {}
/**
Points are counter-clockwise in a Y = down, X = right coordinate
system.
@param reverse If true, the points are reversed (i.e. winding direction is changed)
before the polygon is created.
*/
ConvexPolygon2D(const Array<Vector2>& pts, bool reverse = false);
inline int numVertices() const
{
return m_vertex.size();
}
inline const Vector2& vertex(int index) const
{
debugAssert((index >= 0) && (index <= m_vertex.size()));
return m_vertex[index];
}
/** @param reverseWinding If true, the winding direction of the polygon is reversed for this test.*/
bool contains(const Vector2& p, bool reverseWinding = false) const;
};
} // namespace G3D
#endif

View File

@@ -0,0 +1,506 @@
/**
@file CoordinateFrame.cpp
Coordinate frame class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2001-06-02
@edited 2010-03-13
Copyright 2000-2010, Morgan McGuire.
All rights reserved.
*/
#include "platform.hpp"
#include "CoordinateFrame.hpp"
#include "Quat.hpp"
#include "Matrix4.hpp"
#include "Box.hpp"
#include "AABox.hpp"
#include "Sphere.hpp"
#include "Triangle.hpp"
#include "Ray.hpp"
#include "Capsule.hpp"
#include "Cylinder.hpp"
#include "UprightFrame.hpp"
#include "stringutils.hpp"
#include "PhysicsFrame.hpp"
#include "UprightFrame.hpp"
namespace G3D
{
std::string CoordinateFrame::toXYZYPRDegreesString() const
{
UprightFrame uframe(*this);
return format("CFrame::fromXYZYPRDegrees(% 5.1ff, % 5.1ff, % 5.1ff, % 5.1ff, % 5.1ff, % 5.1ff)", uframe.translation.x, uframe.translation.y,
uframe.translation.z, toDegrees(uframe.yaw), toDegrees(uframe.pitch), 0.0f);
}
CoordinateFrame CoordinateFrame::fromXYZYPRRadians(float x, float y, float z, float yaw, float pitch, float roll)
{
Matrix3 rotation = Matrix3::fromAxisAngleFast(Vector3::unitY(), yaw);
rotation = Matrix3::fromAxisAngleFast(rotation.column(0), pitch) * rotation;
rotation = Matrix3::fromAxisAngleFast(rotation.column(2), roll) * rotation;
const Vector3 translation(x, y, z);
return CoordinateFrame(rotation, translation);
}
void CoordinateFrame::getXYZYPRRadians(float& x, float& y, float& z, float& yaw, float& pitch, float& roll) const
{
x = translation.x;
y = translation.y;
z = translation.z;
const Vector3& look = lookVector();
if (abs(look.y) > 0.99f)
{
// Looking nearly straight up or down
yaw = G3D::pi() + atan2(look.x, look.z);
pitch = asin(look.y);
roll = 0.0f;
}
else
{
// Yaw cannot be affected by others, so pull it first
yaw = G3D::pi() + atan2(look.x, look.z);
// Pitch is the elevation of the yaw vector
pitch = asin(look.y);
Vector3 actualRight = rightVector();
Vector3 expectedRight = look.cross(Vector3::unitY());
roll = 0; // acos(actualRight.dot(expectedRight)); TODO
}
}
void CoordinateFrame::getXYZYPRDegrees(float& x, float& y, float& z, float& yaw, float& pitch, float& roll) const
{
getXYZYPRRadians(x, y, z, yaw, pitch, roll);
yaw = toDegrees(yaw);
pitch = toDegrees(pitch);
roll = toDegrees(roll);
}
CoordinateFrame CoordinateFrame::fromXYZYPRDegrees(float x, float y, float z, float yaw, float pitch, float roll)
{
return fromXYZYPRRadians(x, y, z, toRadians(yaw), toRadians(pitch), toRadians(roll));
}
Ray CoordinateFrame::lookRay() const
{
return Ray::fromOriginAndDirection(translation, lookVector());
}
bool CoordinateFrame::fuzzyEq(const CoordinateFrame& other) const
{
for (int c = 0; c < 3; ++c)
{
for (int r = 0; r < 3; ++r)
{
if (!G3D::fuzzyEq(other.rotation[r][c], rotation[r][c]))
{
return false;
}
}
if (!G3D::fuzzyEq(translation[c], other.translation[c]))
{
return false;
}
}
return true;
}
// Aya
bool CoordinateFrame::fuzzyEq(const CoordinateFrame& other, double absepsilon) const
{
for (int c = 0; c < 3; ++c)
{
for (int r = 0; r < 3; ++r)
{
if (!G3D::fuzzyEq(other.rotation[r][c], rotation[r][c], absepsilon))
{
return false;
}
}
if (!G3D::fuzzyEq(translation[c], other.translation[c], absepsilon))
{
return false;
}
}
return true;
}
// =============
bool CoordinateFrame::fuzzyIsIdentity() const
{
const Matrix3& I = Matrix3::identity();
for (int c = 0; c < 3; ++c)
{
for (int r = 0; r < 3; ++r)
{
if (fuzzyNe(I[r][c], rotation[r][c]))
{
return false;
}
}
if (fuzzyNe(translation[c], 0))
{
return false;
}
}
return true;
}
bool CoordinateFrame::isIdentity() const
{
return (translation == Vector3::zero()) && (rotation == Matrix3::identity());
}
Matrix4 CoordinateFrame::toMatrix4() const
{
return Matrix4(*this);
}
std::string CoordinateFrame::toXML() const
{
return G3D::format("<COORDINATEFRAME>\n %lf,%lf,%lf,%lf,\n %lf,%lf,%lf,%lf,\n %lf,%lf,%lf,%lf,\n %lf,%lf,%lf,%lf\n</COORDINATEFRAME>\n",
rotation[0][0], rotation[0][1], rotation[0][2], translation.x, rotation[1][0], rotation[1][1], rotation[1][2], translation.y, rotation[2][0],
rotation[2][1], rotation[2][2], translation.z, 0.0, 0.0, 0.0, 1.0);
}
// Aya
#if 0
Plane CoordinateFrame::toObjectSpace(const Plane& p) const {
Vector3 N, P;
double d;
p.getEquation(N, d);
P = N * (float)d;
P = pointToObjectSpace(P);
N = normalToObjectSpace(N);
return Plane(N, P);
}
Plane CoordinateFrame::toWorldSpace(const Plane& p) const {
Vector3 N, P;
double d;
p.getEquation(N, d);
P = N * (float)d;
P = pointToWorldSpace(P);
N = normalToWorldSpace(N);
return Plane(N, P);
}
#else
Plane CoordinateFrame::toObjectSpace(const Plane& p) const
{
return Plane(normalToObjectSpace(p.normal()), pointToObjectSpace(p.normal() * p.distance()));
}
Plane CoordinateFrame::toWorldSpace(const Plane& p) const
{
return Plane(normalToWorldSpace(p.normal()), pointToWorldSpace(p.normal() * p.distance()));
}
#endif
// =============
Triangle CoordinateFrame::toObjectSpace(const Triangle& t) const
{
return Triangle(pointToObjectSpace(t.vertex(0)), pointToObjectSpace(t.vertex(1)), pointToObjectSpace(t.vertex(2)));
}
// Aya
Line CoordinateFrame::toWorldSpace(const Line& l) const
{
return Line::fromPointAndUnitDirection(pointToWorldSpace(l.point()), vectorToWorldSpace(l.direction()));
}
//========
Triangle CoordinateFrame::toWorldSpace(const Triangle& t) const
{
return Triangle(pointToWorldSpace(t.vertex(0)), pointToWorldSpace(t.vertex(1)), pointToWorldSpace(t.vertex(2)));
}
Cylinder CoordinateFrame::toWorldSpace(const Cylinder& c) const
{
return Cylinder(pointToWorldSpace(c.point(0)), pointToWorldSpace(c.point(1)), c.radius());
}
Capsule CoordinateFrame::toWorldSpace(const Capsule& c) const
{
return Capsule(pointToWorldSpace(c.point(0)), pointToWorldSpace(c.point(1)), c.radius());
}
Box CoordinateFrame::toWorldSpace(const AABox& b) const
{
Box b2(b);
return toWorldSpace(b2);
}
Box CoordinateFrame::toWorldSpace(const Box& b) const
{
Box out(b);
for (int i = 0; i < 8; ++i)
{
out._corner[i] = pointToWorldSpace(b._corner[i]);
debugAssert(!isNaN(out._corner[i].x));
}
for (int i = 0; i < 3; ++i)
{
out._axis[i] = vectorToWorldSpace(b._axis[i]);
}
out._center = pointToWorldSpace(b._center);
return out;
}
Box CoordinateFrame::toObjectSpace(const Box& b) const
{
return inverse().toWorldSpace(b);
}
Box CoordinateFrame::toObjectSpace(const AABox& b) const
{
return toObjectSpace(Box(b));
}
AABox CoordinateFrame::AABBtoWorldSpace(const AABox& b) const
{
Vector3 center = b.center();
Vector3 halfSize = b.extent() * 0.5f;
Vector3 newCenter = pointToWorldSpace(center);
Vector3 newHalfSize = Vector3(fabs(rotation[0][0]) * halfSize[0] + fabs(rotation[0][1]) * halfSize[1] + fabs(rotation[0][2]) * halfSize[2],
fabs(rotation[1][0]) * halfSize[0] + fabs(rotation[1][1]) * halfSize[1] + fabs(rotation[1][2]) * halfSize[2],
fabs(rotation[2][0]) * halfSize[0] + fabs(rotation[2][1]) * halfSize[1] + fabs(rotation[2][2]) * halfSize[2]);
return AABox(newCenter - newHalfSize, newCenter + newHalfSize);
}
AABox CoordinateFrame::AABBtoObjectSpace(const AABox& b) const
{
Vector3 center = b.center();
Vector3 halfSize = b.extent() * 0.5f;
Vector3 newCenter = pointToObjectSpace(center);
Vector3 newHalfSize = Vector3(fabs(rotation[0][0]) * halfSize[0] + fabs(rotation[1][0]) * halfSize[1] + fabs(rotation[2][0]) * halfSize[2],
fabs(rotation[0][1]) * halfSize[0] + fabs(rotation[1][1]) * halfSize[1] + fabs(rotation[2][1]) * halfSize[2],
fabs(rotation[0][2]) * halfSize[0] + fabs(rotation[1][2]) * halfSize[1] + fabs(rotation[2][2]) * halfSize[2]);
return AABox(newCenter - newHalfSize, newCenter + newHalfSize);
}
Sphere CoordinateFrame::toWorldSpace(const Sphere& b) const
{
return Sphere(pointToWorldSpace(b.center), b.radius);
}
Sphere CoordinateFrame::toObjectSpace(const Sphere& b) const
{
return Sphere(pointToObjectSpace(b.center), b.radius);
}
Ray CoordinateFrame::toWorldSpace(const Ray& r) const
{
return Ray::fromOriginAndDirection(pointToWorldSpace(r.origin()), vectorToWorldSpace(r.direction()));
}
Ray CoordinateFrame::toObjectSpace(const Ray& r) const
{
return Ray::fromOriginAndDirection(pointToObjectSpace(r.origin()), vectorToObjectSpace(r.direction()));
}
Aya::RbxRay CoordinateFrame::toObjectSpace(const Aya::RbxRay& r) const
{
return Aya::RbxRay::fromOriginAndDirection(pointToObjectSpace(r.origin()), vectorToObjectSpace(r.direction()));
}
void CoordinateFrame::lookAt(const Vector3& target)
{
lookAt(target, Vector3::unitY());
}
void CoordinateFrame::lookAt(const Vector3& target, Vector3 up)
{
up = up.direction();
Vector3 look = (target - translation).direction();
if (fabs(look.dot(up)) > .99f)
{
up = Vector3::unitX();
if (fabs(look.dot(up)) > .99f)
{
up = Vector3::unitY();
}
}
up -= look * look.dot(up);
up.unitize();
Vector3 z = -look;
Vector3 x = -z.cross(up);
x.unitize();
Vector3 y = z.cross(x);
rotation.setColumn(0, x);
rotation.setColumn(1, y);
rotation.setColumn(2, z);
}
CoordinateFrame CoordinateFrame::lerp(const CoordinateFrame& other, float alpha) const
{
if (alpha == 1.0f)
{
return other;
}
else if (alpha == 0.0f)
{
return *this;
}
else
{
const Quat q1(this->rotation);
const Quat q2(other.rotation);
return CoordinateFrame(q1.slerp(q2, alpha).toRotationMatrix(), translation * (1 - alpha) + other.translation * alpha);
}
}
CoordinateFrame CoordinateFrame::nlerp(const CoordinateFrame& other, float alpha) const
{
if (alpha == 1.0f)
{
return other;
}
else if (alpha == 0.0f)
{
return *this;
}
else
{
const Quat q1(this->rotation);
const Quat q2(other.rotation);
return CoordinateFrame(q1.nlerp(q2, alpha).toRotationMatrix(), translation * (1 - alpha) + other.translation * alpha);
}
}
void CoordinateFrame::pointToWorldSpace(const Array<Vector3>& v, Array<Vector3>& vout) const
{
vout.resize(v.size());
for (int i = 0; i < v.size(); ++i)
{
vout[i] = pointToWorldSpace(v[i]);
}
}
void CoordinateFrame::normalToWorldSpace(const Array<Vector3>& v, Array<Vector3>& vout) const
{
vout.resize(v.size());
for (int i = 0; i < v.size(); ++i)
{
vout[i] = normalToWorldSpace(v[i]);
}
}
void CoordinateFrame::vectorToWorldSpace(const Array<Vector3>& v, Array<Vector3>& vout) const
{
vout.resize(v.size());
for (int i = v.size() - 1; i >= 0; --i)
{
vout[i] = vectorToWorldSpace(v[i]);
}
}
void CoordinateFrame::pointToObjectSpace(const Array<Vector3>& v, Array<Vector3>& vout) const
{
vout.resize(v.size());
for (int i = v.size() - 1; i >= 0; --i)
{
vout[i] = pointToObjectSpace(v[i]);
}
}
void CoordinateFrame::normalToObjectSpace(const Array<Vector3>& v, Array<Vector3>& vout) const
{
vout.resize(v.size());
for (int i = v.size() - 1; i >= 0; --i)
{
vout[i] = normalToObjectSpace(v[i]);
}
}
void CoordinateFrame::vectorToObjectSpace(const Array<Vector3>& v, Array<Vector3>& vout) const
{
vout.resize(v.size());
for (int i = v.size() - 1; i >= 0; --i)
{
vout[i] = vectorToObjectSpace(v[i]);
}
}
btTransform CoordinateFrame::transformFromCFrame() const
{
btMatrix3x3 rot(rotation[0][0], rotation[0][1], rotation[0][2], rotation[1][0], rotation[1][1], rotation[1][2], rotation[2][0], rotation[2][1],
rotation[2][2]);
return btTransform(rot, btVector3(translation.x, translation.y, translation.z));
}
} // namespace G3D

View File

@@ -0,0 +1,372 @@
/**
@file CoordinateFrame.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2001-03-04
@edited 2009-04-29
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_CFrame_h
#define G3D_CFrame_h
#include "platform.hpp"
#include "Vector3.hpp"
#include "Vector4.hpp"
#include "Matrix3.hpp"
#include "Array.hpp"
#include "Line.hpp"
#include <math.h>
#include <string>
#include <stdio.h>
#include <cstdarg>
#include <assert.h>
// Aya
#include "RbxRay.hpp"
// ====
// Bullet
#include "LinearMath/btTransform.hpp"
// ====
#ifdef _MSC_VER
// Turn off "conditional expression is constant" warning; MSVC generates this
// for debug assertions in inlined methods.
#pragma warning(disable : 4127)
#endif
namespace G3D
{
/**
A rigid body RT (rotation-translation) transformation.
CoordinateFrame abstracts a 4x4 matrix that maps object space to world space:
v_world = C * v_object
CoordinateFrame::rotation is the upper 3x3 submatrix, CoordinateFrame::translation
is the right 3x1 column. The 4th row is always [0 0 0 1], so it isn't stored.
So you don't have to remember which way the multiplication and transformation work,
it provides explicit toWorldSpace and toObjectSpace methods. Also, points, vectors
(directions), and surface normals transform differently, so they have separate methods.
Some helper functions transform whole primitives like boxes in and out of object space.
Convert to Matrix4 using CoordinateFrame::toMatrix4. You <I>can</I> construct a CoordinateFrame
from a Matrix4 using Matrix4::approxCoordinateFrame, however, because a Matrix4 is more
general than a CoordinateFrame, some information may be lost.
@sa G3D::UprightFrame, G3D::PhysicsFrame, G3D::Matrix4, G3D::Quat
*/
class CoordinateFrame
{
public:
/** Takes object space points to world space. */
Matrix3 rotation;
/** Takes object space points to world space. */
Vector3 translation;
inline bool operator==(const CoordinateFrame& other) const
{
return (translation == other.translation) && (rotation == other.rotation);
}
inline bool operator!=(const CoordinateFrame& other) const
{
return !(*this == other);
}
bool fuzzyEq(const CoordinateFrame& other) const;
// Aya
bool fuzzyEq(const CoordinateFrame& other, double absepsilon) const;
// =====
bool fuzzyIsIdentity() const;
bool isIdentity() const;
/**
Initializes to the identity coordinate frame.
*/
CoordinateFrame()
: rotation(Matrix3::identity())
, translation(Vector3::zero())
{
}
CoordinateFrame(const Vector3& _translation)
: rotation(Matrix3::identity())
, translation(_translation)
{
}
CoordinateFrame(const Matrix3& rotation, const Vector3& translation)
: rotation(rotation)
, translation(translation)
{
}
CoordinateFrame(const Matrix3& rotation)
: rotation(rotation)
, translation(Vector3::zero())
{
}
static CoordinateFrame fromXYZYPRRadians(float x, float y, float z, float yaw = 0.0f, float pitch = 0.0f, float roll = 0.0f);
std::string toXYZYPRDegreesString() const;
/** Construct a coordinate frame from translation = (x,y,z) and
rotations (in that order) about Y, object space X, object space
Z. Note that because object-space axes are used, these are not
equivalent to Euler angles; they are known as Tait-Bryan
rotations and are more convenient for intuitive positioning.*/
static CoordinateFrame fromXYZYPRDegrees(float x, float y, float z, float yaw = 0.0f, float pitch = 0.0f, float roll = 0.0f);
/**
Computes the inverse of this coordinate frame.
*/
inline CoordinateFrame inverse() const
{
CoordinateFrame out;
out.rotation = rotation.transpose();
out.translation = -out.rotation * translation;
return out;
}
inline ~CoordinateFrame() {}
/** See also Matrix4::approxCoordinateFrame */
class Matrix4 toMatrix4() const;
void getXYZYPRRadians(float& x, float& y, float& z, float& yaw, float& pitch, float& roll) const;
void getXYZYPRDegrees(float& x, float& y, float& z, float& yaw, float& pitch, float& roll) const;
/**
Produces an XML serialization of this coordinate frame.
@deprecated
*/
std::string toXML() const;
/**
Returns the heading of the lookVector as an angle in radians relative to
the world -z axis. That is, a counter-clockwise heading where north (-z)
is 0 and west (-x) is PI/2.
Note that the heading ignores the Y axis, so an inverted
object has an inverted heading.
*/
inline float getHeading() const
{
Vector3 look = rotation.column(2);
float angle = -(float)atan2(-look.x, look.z);
return angle;
}
/**
Takes the coordinate frame into object space.
this->inverse() * c
*/
inline CoordinateFrame toObjectSpace(const CoordinateFrame& c) const
{
return this->inverse() * c;
}
inline Vector4 toObjectSpace(const Vector4& v) const
{
return this->inverse().toWorldSpace(v);
}
inline Vector4 toWorldSpace(const Vector4& v) const
{
return Vector4(rotation * Vector3(v.x, v.y, v.z) + translation * v.w, v.w);
}
// Aya
Line toWorldSpace(const Line& l) const;
//======
/**
Transforms the point into world space.
*/
inline Vector3 pointToWorldSpace(const Vector3& v) const
{
return Vector3(rotation[0][0] * v[0] + rotation[0][1] * v[1] + rotation[0][2] * v[2] + translation[0],
rotation[1][0] * v[0] + rotation[1][1] * v[1] + rotation[1][2] * v[2] + translation[1],
rotation[2][0] * v[0] + rotation[2][1] * v[1] + rotation[2][2] * v[2] + translation[2]);
}
/**
Transforms the point into object space. Assumes that the rotation matrix is orthonormal.
*/
inline Vector3 pointToObjectSpace(const Vector3& v) const
{
float p[3];
p[0] = v[0] - translation[0];
p[1] = v[1] - translation[1];
p[2] = v[2] - translation[2];
// debugAssert(G3D::fuzzyEq(rotation.determinant(), 1.0f));
return Vector3(rotation[0][0] * p[0] + rotation[1][0] * p[1] + rotation[2][0] * p[2],
rotation[0][1] * p[0] + rotation[1][1] * p[1] + rotation[2][1] * p[2],
rotation[0][2] * p[0] + rotation[1][2] * p[1] + rotation[2][2] * p[2]);
}
/**
Transforms the vector into world space (no translation).
*/
inline Vector3 vectorToWorldSpace(const Vector3& v) const
{
return rotation * v;
}
inline Vector3 normalToWorldSpace(const Vector3& v) const
{
return rotation * v;
}
class Ray toObjectSpace(const Ray& r) const;
// Aya
Aya::RbxRay toObjectSpace(const Aya::RbxRay& r) const;
// ======
Ray toWorldSpace(const Ray& r) const;
/**
Transforms the vector into object space (no translation).
*/
inline Vector3 vectorToObjectSpace(const Vector3& v) const
{
// Multiply on the left (same as rotation.transpose() * v)
return v * rotation;
}
inline Vector3 normalToObjectSpace(const Vector3& v) const
{
// Multiply on the left (same as rotation.transpose() * v)
return v * rotation;
}
void pointToWorldSpace(const Array<Vector3>& v, Array<Vector3>& vout) const;
void normalToWorldSpace(const Array<Vector3>& v, Array<Vector3>& vout) const;
void vectorToWorldSpace(const Array<Vector3>& v, Array<Vector3>& vout) const;
void pointToObjectSpace(const Array<Vector3>& v, Array<Vector3>& vout) const;
void normalToObjectSpace(const Array<Vector3>& v, Array<Vector3>& vout) const;
void vectorToObjectSpace(const Array<Vector3>& v, Array<Vector3>& vout) const;
class Box toWorldSpace(const class AABox& b) const;
class Box toWorldSpace(const class Box& b) const;
class Cylinder toWorldSpace(const class Cylinder& b) const;
class Capsule toWorldSpace(const class Capsule& b) const;
class Plane toWorldSpace(const class Plane& p) const;
class Sphere toWorldSpace(const class Sphere& b) const;
class Triangle toWorldSpace(const class Triangle& t) const;
class Box toObjectSpace(const AABox& b) const;
class Box toObjectSpace(const Box& b) const;
class Plane toObjectSpace(const Plane& p) const;
class Sphere toObjectSpace(const Sphere& b) const;
Triangle toObjectSpace(const Triangle& t) const;
class AABox AABBtoWorldSpace(const AABox& b) const;
class AABox AABBtoObjectSpace(const AABox& b) const;
/** Compose: create the transformation that is <I>other</I> followed by <I>this</I>.*/
CoordinateFrame operator*(const CoordinateFrame& other) const
{
return CoordinateFrame(rotation * other.rotation, pointToWorldSpace(other.translation));
}
// Aya Added by DB
inline static void mul(const CoordinateFrame& a, const CoordinateFrame& b, CoordinateFrame& answer)
{
Matrix3::fastMul(a.rotation, b.rotation, answer.rotation);
answer.translation = a.pointToWorldSpace(b.translation);
}
// ============
CoordinateFrame operator+(const Vector3& v) const
{
return CoordinateFrame(rotation, translation + v);
}
CoordinateFrame operator-(const Vector3& v) const
{
return CoordinateFrame(rotation, translation - v);
}
void lookAt(const Vector3& target);
void lookAt(const Vector3& target, Vector3 up);
/** The direction this camera is looking (its negative z axis)*/
inline Vector3 lookVector() const
{
return -rotation.column(2);
}
/** Returns the ray starting at the camera origin travelling in direction CoordinateFrame::lookVector. */
class Ray lookRay() const;
/** Up direction for this camera (its y axis). */
inline Vector3 upVector() const
{
return rotation.column(1);
}
inline Vector3 rightVector() const
{
return rotation.column(0);
}
/**
If a viewer looks along the look vector, this is the viewer's "left".
Useful for strafing motions and building alternative coordinate frames.
*/
inline Vector3 leftVector() const
{
return -rotation.column(0);
}
/**
Linearly interpolates between two coordinate frames, using
Quat::slerp for the rotations.
*/
CoordinateFrame lerp(const CoordinateFrame& other, float alpha) const;
// Roblox
CoordinateFrame nlerp(const CoordinateFrame& other, float alpha) const;
////
btTransform transformFromCFrame() const;
};
typedef CoordinateFrame CFrame;
} // namespace G3D
#endif

54
engine/3d/src/Crypto.cpp Normal file
View File

@@ -0,0 +1,54 @@
/**
@file Crypto.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
@created 2006-03-28
@edited 2006-04-06
*/
#include "platform.hpp"
#include "Crypto.hpp"
#include "g3dmath.hpp"
#include <zlib.h>
namespace G3D
{
int Crypto::smallPrime(int n)
{
debugAssert(n < numSmallPrimes() && n >= 0);
// From:
// http://primes.utm.edu/lists/small/1000.txt
static const int table[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109,
113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269,
271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439,
443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617,
619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811,
821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009,
1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163,
1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319,
1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493,
1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663,
1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861,
1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999};
return table[n];
}
int Crypto::numSmallPrimes()
{
return 303;
}
uint32 Crypto::crc32(const void* byte, size_t numBytes)
{
return ::crc32(::crc32(0, Z_NULL, 0), static_cast<const Bytef*>(byte), numBytes);
}
} // namespace G3D

97
engine/3d/src/Crypto.hpp Normal file
View File

@@ -0,0 +1,97 @@
/**
@file Crypto.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2006-03-29
@edited 2006-04-06
*/
#ifndef G3D_CRYPTO_H
#define G3D_CRYPTO_H
#include "platform.hpp"
#include "g3dmath.hpp"
#include <string>
namespace G3D
{
/** See G3D::Crypto::md5 */
class MD5Hash
{
private:
uint8 value[16];
public:
MD5Hash()
{
for (int i = 0; i < 16; ++i)
{
value[i] = 0;
}
}
uint8& operator[](int i)
{
return value[i];
}
const uint8& operator[](int i) const
{
return value[i];
}
bool operator==(const MD5Hash& other) const
{
bool match = true;
for (int i = 0; i < 16; ++i)
{
match = match && (other.value[i] == value[i]);
}
return match;
}
inline bool operator!=(const MD5Hash& other) const
{
return !(*this == other);
}
};
/** Cryptography and hashing helper functions */
class Crypto
{
public:
/**
Computes the CRC32 value of a byte array. CRC32 is designed to be a hash
function that produces different values for similar strings.
This implementation is compatible with PKZIP and GZIP.
Based on http://www.gamedev.net/reference/programming/features/crc32/
*/
static uint32 crc32(const void* bytes, size_t numBytes);
/**
Computes the MD5 hash (message digest) of a byte stream, as defined by
http://www.ietf.org/rfc/rfc1321.txt.
@cite Based on implementation by L. Peter Deutsch, ghost@aladdin.com
*/
MD5Hash md5(const void* bytes, size_t numBytes);
/**
Returns the nth prime less than 2000 in constant time. The first prime has index
0 and is the number 2.
*/
static int smallPrime(int n);
/** Returns 1 + the largest value that can be passed to smallPrime. */
static int numSmallPrimes();
};
} // namespace G3D
#endif

170
engine/3d/src/Cylinder.cpp Normal file
View File

@@ -0,0 +1,170 @@
/**
@file Cylinder.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-02-07
@edited 2006-02-18
Copyright 2000-2006, Morgan McGuire.
All rights reserved.
*/
#include "platform.hpp"
#include "Cylinder.hpp"
#include "LineSegment.hpp"
#include "CoordinateFrame.hpp"
#include "Line.hpp"
#include "AABox.hpp"
namespace G3D
{
Cylinder::Cylinder() {}
Cylinder::Cylinder(const Vector3& _p1, const Vector3& _p2, float _r)
: p1(_p1)
, p2(_p2)
, mRadius(_r)
{
}
Line Cylinder::axis() const
{
return Line::fromTwoPoints(p1, p2);
}
float Cylinder::radius() const
{
return mRadius;
}
float Cylinder::volume() const
{
return (float)pi() * square(mRadius) * (p1 - p2).magnitude();
}
float Cylinder::area() const
{
return
// Sides
(twoPi() * mRadius) * height() +
// Caps
twoPi() * square(mRadius);
}
void Cylinder::getBounds(AABox& out) const
{
Vector3 min = p1.min(p2) - (Vector3(1, 1, 1) * mRadius);
Vector3 max = p1.max(p2) + (Vector3(1, 1, 1) * mRadius);
out = AABox(min, max);
}
bool Cylinder::contains(const Vector3& p) const
{
return LineSegment::fromTwoPoints(p1, p2).distanceSquared(p) <= square(mRadius);
}
void Cylinder::getReferenceFrame(CoordinateFrame& cframe) const
{
cframe.translation = center();
Vector3 Y = (p1 - p2).direction();
Vector3 X = (abs(Y.dot(Vector3::unitX())) > 0.9) ? Vector3::unitY() : Vector3::unitX();
Vector3 Z = X.cross(Y).direction();
X = Y.cross(Z);
cframe.rotation.setColumn(0, X);
cframe.rotation.setColumn(1, Y);
cframe.rotation.setColumn(2, Z);
}
void Cylinder::getRandomSurfacePoint(Vector3& p, Vector3& N) const
{
float h = height();
float r = radius();
// Create a random point on a standard cylinder and then rotate to the global frame.
// Relative areas (factor of 2PI already taken out)
float capRelArea = square(r) / 2.0f;
float sideRelArea = r * h;
float r1 = uniformRandom(0, capRelArea * 2 + sideRelArea);
if (r1 < capRelArea * 2)
{
// Select a point uniformly at random on a disk
// @cite http://mathworld.wolfram.com/DiskPointPicking.html
float a = uniformRandom(0, (float)twoPi());
float r2 = sqrt(uniformRandom(0, 1)) * r;
p.x = cos(a) * r2;
p.z = sin(a) * r2;
N.x = 0;
N.z = 0;
if (r1 < capRelArea)
{
// Top
p.y = h / 2.0f;
N.y = 1;
}
else
{
// Bottom
p.y = -h / 2.0f;
N.y = -1;
}
}
else
{
// Side
float a = uniformRandom(0, (float)twoPi());
N.x = cos(a);
N.y = 0;
N.z = sin(a);
p.x = N.x * r;
p.z = N.y * r;
p.y = uniformRandom(-h / 2.0f, h / 2.0f);
}
// Transform to world space
CoordinateFrame cframe;
getReferenceFrame(cframe);
p = cframe.pointToWorldSpace(p);
N = cframe.normalToWorldSpace(N);
}
Vector3 Cylinder::randomInteriorPoint() const
{
float h = height();
float r = radius();
// Create a random point in a standard cylinder and then rotate to the global frame.
// Select a point uniformly at random on a disk
// @cite http://mathworld.wolfram.com/DiskPointPicking.html
float a = uniformRandom(0, (float)twoPi());
float r2 = sqrt(uniformRandom(0, 1)) * r;
Vector3 p(cos(a) * r2, uniformRandom(-h / 2.0f, h / 2.0f), sin(a) * r2);
// Transform to world space
CoordinateFrame cframe;
getReferenceFrame(cframe);
return cframe.pointToWorldSpace(p);
}
} // namespace G3D

View File

@@ -0,0 +1,94 @@
/**
@file Cylinder.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-02-07
@edited 2005-09-26
Copyright 2000-2005, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_Cylinder_H
#define G3D_Cylinder_H
#include "platform.hpp"
#include "g3dmath.hpp"
#include "Vector3.hpp"
namespace G3D
{
class Line;
class AABox;
/**
Right cylinder
*/
class Cylinder
{
private:
Vector3 p1;
Vector3 p2;
float mRadius;
public:
/** Uninitialized */
Cylinder();
Cylinder(const Vector3& _p1, const Vector3& _p2, float _r);
/** The line down the center of the Cylinder */
Line axis() const;
/**
A reference frame in which the center of mass is at the origin and
the Y-axis is the cylinder's axis. If the cylinder is transformed, this reference frame
may freely rotate around its axis.*/
void getReferenceFrame(class CoordinateFrame& cframe) const;
/** Returns point 0 or 1 */
inline const Vector3& point(int i) const
{
debugAssert(i >= 0 && i <= 1);
return (i == 0) ? p1 : p2;
}
/**
Returns true if the point is inside the Cylinder or on its surface.
*/
bool contains(const Vector3& p) const;
float area() const;
float volume() const;
float radius() const;
/** Center of mass */
inline Vector3 center() const
{
return (p1 + p2) / 2.0f;
}
inline float height() const
{
return (p1 - p2).magnitude();
}
/**
Get close axis aligned bounding box.
With vertical world orientation, the top and bottom might not be very tight. */
void getBounds(AABox& out) const;
/** Random world space point with outward facing normal. */
void getRandomSurfacePoint(Vector3& P, Vector3& N) const;
/** Point selected uniformly at random over the volume. */
Vector3 randomInteriorPoint() const;
};
} // namespace G3D
#endif

173
engine/3d/src/Draw.cpp Normal file
View File

@@ -0,0 +1,173 @@
#include "Draw.hpp"
#include "DrawPrimitives.hpp"
#include "DrawAdorn.hpp"
#include "Base/Part.hpp"
#include "Base/Adorn.hpp"
#include "Utility/Math.hpp"
#include "DataModel/PartInstance.hpp"
namespace Aya
{
static const int SelectionBoxLineThreshold = 1500; //!< tweakable threshold before boxes switch to lines
bool Draw::m_showHoverOver = true;
G3D::Color4 Draw::m_hoverOverColor(0.7f, 0.9f, 1.0f, 1.0f); // 178, 229, 255
G3D::Color4 Draw::m_selectColor(0.1f, 0.6f, 1.0f, 1.0f); // 25, 153, 256
void Draw::partAdorn(const Part& part, Adorn* adorn, const G3D::Color3& controllerColor)
{
adornSurfaces(part, adorn, controllerColor);
}
/**
* Set color used for the selection box when a part is selected.
*/
void Draw::setSelectColor(const G3D::Color4& Color)
{
m_selectColor = Color;
}
/**
* Set the color used for the selection box when a part is mouse hover over.
*/
void Draw::setHoverOverColor(const G3D::Color4& color)
{
m_hoverOverColor = color;
}
void Draw::selectionBox(const Part& part, Adorn* adorn, SelectState selectState, float lineThickness)
{
if (selectState == SELECT_HOVER && m_showHoverOver)
{
adorn->setObjectToWorldMatrix(part.coordinateFrame);
selectionBox(part, adorn, m_hoverOverColor, lineThickness * 2);
}
else if (selectState != SELECT_HOVER)
{
selectionBox(part, adorn, (selectState == SELECT_NORMAL) ? selectColor() : Color3::orange(), lineThickness);
}
}
void Draw::selectionBox(ModelInstance& model, Adorn* adorn, const G3D::Color4& selectColor, float lineThickness)
{
selectionBox(model.computePart(), adorn, selectColor, lineThickness);
}
void Draw::adornSurfaces(const Part& part, Adorn* adorn, const G3D::Color3& controllerColor)
{
for (int face = 0; face < 6; ++face)
{
switch (part.surfaceType[face])
{
default:
break;
case ROTATE:
case ROTATE_P:
case ROTATE_V:
Draw::constraint(part, adorn, face, controllerColor);
break;
}
}
}
void Draw::constraint(const Part& part, Adorn* adorn, int face, const G3D::Color3& controllerColor)
{
SurfaceType surfaceType = part.surfaceType[face];
debugAssert((surfaceType == ROTATE) || (surfaceType == ROTATE_P) || (surfaceType == ROTATE_V));
Vector3 halfSize = part.gridSize * 0.5;
const Matrix3& relativeRotation = Math::getAxisRotationMatrix(face);
Vector3 relativeTranslation;
int normal = face % 3;
float posNeg = face > 2 ? -1.0f : 1.0f;
relativeTranslation[normal] = halfSize[normal] * posNeg;
CoordinateFrame translation(relativeRotation, relativeTranslation);
CoordinateFrame newObject = part.coordinateFrame * translation;
adorn->setObjectToWorldMatrix(newObject);
float axis = 1.0f;
float radius = 0.2f;
adorn->cylinderAlongX(radius, axis, Color3::yellow());
if ((surfaceType == ROTATE_V) || (surfaceType == ROTATE_P))
{
float axis = 0.25f;
float radius = 0.4f;
adorn->cylinderAlongX(radius, axis, controllerColor);
}
}
void Draw::selectionBox(const Part& part, Adorn* adorn, const G3D::Color4& selectColor, float lineThickness)
{
if (adorn->getCamera() != NULL)
{
// determine distance from camera to object
const Vector3 camera_pos = adorn->getCamera()->getCameraCoordinateFrame().translation;
const Vector3 delta = part.coordinateFrame.translation - camera_pos;
const float delta_squared = delta.squaredLength();
// determine size of object by using longest axis
const float grid_size = part.gridSize[part.gridSize.primaryAxis()];
const float grid_size_squared = grid_size * grid_size;
// make sure the distance is greater than the size
if (delta_squared > grid_size_squared)
{
// adjust distance based on thickness of lines
const float threshold_dist = SelectionBoxLineThreshold * lineThickness;
const float threshold_dist_squared = threshold_dist * threshold_dist;
// subtract the size from the distance and compare to the threshold
if (delta_squared - grid_size_squared > threshold_dist_squared)
{
// switch to drawing lines
const Vector3 half_size = part.gridSize / 2;
adorn->setObjectToWorldMatrix(part.coordinateFrame);
DrawAdorn::outlineBox(adorn, Extents(-half_size, half_size), selectColor);
return;
}
}
}
adorn->setObjectToWorldMatrix(part.coordinateFrame);
Vector3 halfSize = 0.5f * part.gridSize;
float highlight = lineThickness;
for (int c1 = 0; c1 < 3; ++c1)
{ // x, y, z
int c2 = (c1 + 1) % 3; // y
int c3 = (c1 + 2) % 3; // z
for (int d2 = -1; d2 < 2; d2 += 2)
{
for (int d3 = -1; d3 < 2; d3 += 2)
{
// for c1, shorten the length of the edge segment by highlight size
// on both ends so that the edge segments do not overlap on the
// corners
Vector3 p0, p1;
p0[c1] = -halfSize[c1] - (c1 == 0 ? highlight : -highlight);
p0[c2] = d2 * halfSize[c2] - highlight;
p0[c3] = d3 * halfSize[c3] - highlight;
p1[c1] = halfSize[c1] + (c1 == 0 ? highlight : -highlight);
p1[c2] = d2 * halfSize[c2] + highlight;
p1[c3] = d3 * halfSize[c3] + highlight;
if (p0.x <= p1.x && p0.y <= p1.y && p0.z <= p1.z)
adorn->box(AABox(p0, p1), selectColor);
}
}
}
}
} // namespace Aya

74
engine/3d/src/Draw.hpp Normal file
View File

@@ -0,0 +1,74 @@
#pragma once
#include "SelectState.hpp"
#include "Utility/G3DCore.hpp"
#include "DataModel/ModelInstance.hpp"
#include <vector>
namespace G3D
{
class Rect2D;
class CoordinateFrame;
class Ray;
class RenderDevice;
class Color4;
} // namespace G3D
namespace Aya
{
class Part;
class Details;
class Adorn;
class Draw
{
private:
// generic
static void adornSurfaces(const Part& part, Adorn* adorn, const G3D::Color3& controllerColor);
static void frameBox(const Part& part, Adorn* adorn, const Color4& color);
static void constraint(const Part& part, Adorn* adorn, int face, const G3D::Color3& controllerColor);
static bool m_showHoverOver;
static G3D::Color4 m_hoverOverColor;
static G3D::Color4 m_selectColor;
static G3D::Color4 m_ayaSelectColor;
public:
static const G3D::Color4& selectColor()
{
return m_selectColor;
}
static void setSelectColor(const G3D::Color4& Color);
static const G3D::Color4& hoverOverColor()
{
return m_hoverOverColor;
}
static void setHoverOverColor(const G3D::Color4& color);
// DRAWING - assumes 2D mode
static void selectionSquare(const G3D::Rect2D& rect, float thick);
static void partAdorn(const Part& part, Adorn* adorn, const G3D::Color3& controllerColor);
static void selectionBox(const Part& part, Adorn* adorn, const G3D::Color4& selectColor, float lineThickness = 0.15f);
static void selectionBox(ModelInstance& model, Adorn* adorn, const G3D::Color4& selectColor, float lineThickness = 0.15f);
static void selectionBox(const Part& part, Adorn* adorn, SelectState selectState, float lineThickness = 0.15f);
static void showHoverOver(bool Show)
{
m_showHoverOver = Show;
}
static bool isHoverOver()
{
return m_showHoverOver;
}
};
} // namespace Aya

981
engine/3d/src/DrawAdorn.cpp Normal file
View File

@@ -0,0 +1,981 @@
#include "DrawAdorn.hpp"
#include "DrawPrimitives.hpp"
#include "Base/Part.hpp"
#include "Base/Adorn.hpp"
#include "HitTest.hpp"
#include "Utility/Math.hpp"
#include "Utility/Rect.hpp"
#include "Utility/Extents.hpp"
#include "Utility/HitTest.hpp"
#include "Utility/Face.hpp"
#include "Base/MeshGen.hpp"
#include "DataModel/Camera.hpp"
#include "DataModel/PartInstance.hpp"
#include "Color3uint8.hpp"
FASTFLAGVARIABLE(Studio3DGridUseAALines, true)
namespace Aya
{
static const float ZEROPLANE_GRID_SIZE_BASE = 400.0f;
static const float ZEROPLANE_GRID_FACTOR = 8.0f;
static const float HandleOffset = 0.5f;
static const float RotationGridLineThickness = 0.045f;
static const float RotationGridLineMinAngle = Math::degreesToRadians(2.5f);
static const float TorusThickness = 0.025f;
static const float TorusThicknessMinAngle = Math::degreesToRadians(2.5f);
static const float SphereRadius = 0.5f;
static const float SphereMinAngle = Math::degreesToRadians(2.0f);
static const float ArrowLength = 3.0f;
static const float ArrowMinAngle = Math::degreesToRadians(3.5f);
static const float HandleTransparency = 0.65f;
static const float AxisTransparency = 0.28f;
const Color3 DrawAdorn::axisColors[3] = {Color3::red(), Color3::green(), Color3::blue()};
// common colors
const Color3 DrawAdorn::beige(G3D::Color3uint8(0xF5, 0xF5, 0xDC));
const Color3 DrawAdorn::darkblue(G3D::Color3uint8(0x00, 0x00, 0x8B));
const Color3 DrawAdorn::powderblue(G3D::Color3uint8(0xB0, 0xE0, 0xE6));
const Color3 DrawAdorn::skyblue(G3D::Color3uint8(0x87, 0xCE, 0xEB));
const Color3 DrawAdorn::violet(G3D::Color3uint8(0xEE, 0x82, 0xEE));
const Color3 DrawAdorn::slategray(G3D::Color3uint8(0x70, 0x80, 0x90));
const Color3 DrawAdorn::aqua(G3D::Color3uint8(0x00, 0xFF, 0xFF));
const Color3 DrawAdorn::tan(G3D::Color3uint8(0xD2, 0xB4, 0x8C));
const Color3 DrawAdorn::wheat(G3D::Color3uint8(0xF5, 0xDE, 0xB3));
const Color3 DrawAdorn::cornflowerblue(G3D::Color3uint8(0x64, 0x95, 0xED));
const Color3 DrawAdorn::limegreen(G3D::Color3uint8(0x32, 0xCD, 0x32));
const Color3 DrawAdorn::magenta(G3D::Color3uint8(0xFF, 0x00, 0xFF));
const Color3 DrawAdorn::pink(G3D::Color3uint8(0xFF, 0xC0, 0xCB));
const Color3 DrawAdorn::silver(G3D::Color3uint8(0xC0, 0xC0, 0xC0));
void DrawAdorn::cylinder(Adorn* adorn, const CoordinateFrame& worldC, int axis, float length, float radius, const Color4& color, bool cap)
{
const Matrix3& relativeRotation = Math::getAxisRotationMatrix(axis);
CoordinateFrame rotatedWorldC(worldC.rotation * relativeRotation, worldC.translation);
adorn->setObjectToWorldMatrix(rotatedWorldC);
adorn->cylinderAlongX(radius, length, color, cap);
}
void DrawAdorn::faceInWorld(Adorn* adorn, const Face& face, float thickness, const Color4& color)
{
Vector3 x = (face[1] - face[0]);
Vector3 y = (face[3] - face[0]);
Vector3 z = x.cross(y);
Vector3 center = (face[0] + face[2]) * 0.5;
CoordinateFrame c(center);
c.rotation.setColumn(0, x.unit());
c.rotation.setColumn(1, y.unit());
c.rotation.setColumn(2, z.unit());
adorn->setObjectToWorldMatrix(c);
float dx = x.magnitude() * 0.5f;
float dy = y.magnitude() * 0.5f;
Extents extents(Vector3(-dx, -dy, -thickness), Vector3(dx, dy, thickness));
adorn->box(extents, color);
}
void DrawAdorn::surfaceBorder(Adorn* adorn, const Vector3& halfRealSize, float highlight, int surfaceId, const Color4& color)
{
int c1 = surfaceId % 3; // x, y, or z - primary
float cZ = halfRealSize[c1] * ((surfaceId < 3) ? 1 : -1);
Vector3 p0, p1;
p0[c1] = cZ - highlight;
p1[c1] = cZ + highlight;
int c2 = (c1 + 1) % 3; // y
int c3 = (c1 + 2) % 3; // z
for (int direction = 0; direction < 2; ++direction)
{
int cY = direction ? c2 : c3;
int cZ = direction ? c3 : c2;
for (int polarity = -1; polarity <= 1; polarity += 2)
{
p0[cY] = polarity * halfRealSize[cY] - highlight;
p1[cY] = polarity * halfRealSize[cY] + highlight;
p0[cZ] = -halfRealSize[cZ] - highlight;
p1[cZ] = halfRealSize[cZ] + highlight;
AABox(p0, p1);
adorn->box(AABox(p0, p1), color);
}
}
}
void DrawAdorn::surfaceGridOnFace(const Primitive& prim, Adorn* adorn, int surfaceId, const Color4& color, int boxesPerStud)
{
CoordinateFrame bodyCoord = prim.getCoordinateFrame();
CoordinateFrame faceCoord = bodyCoord * prim.getConstGeometry()->getSurfaceCoordInBody(surfaceId);
Vector4 bounds(-1e10f, -1e10f, 1e10f, 1e10f);
for (int i = 0; i < prim.getConstGeometry()->getNumVertsInSurface(surfaceId); i++)
{
Vector3 vertInBody = prim.getConstGeometry()->getSurfaceVertInBody(surfaceId, i);
Vector3 vertInWorld = bodyCoord.pointToWorldSpace(vertInBody);
Vector3 vertInFace = faceCoord.pointToObjectSpace(vertInWorld);
// max vector
if (vertInFace.x > bounds.x)
bounds.x = vertInFace.x;
if (vertInFace.y > bounds.y)
bounds.y = vertInFace.y;
// Min vector
if (vertInFace.x < bounds.z)
bounds.z = vertInFace.x;
if (vertInFace.y < bounds.w)
bounds.w = vertInFace.y;
}
surfaceGridAtCoord(adorn, faceCoord, bounds, Vector3::unitX(), Vector3::unitY(), color, boxesPerStud);
}
void DrawAdorn::zeroPlaneGrid(Adorn* adorn, const Aya::Camera& camera, const int studsPerBox, const int yLevel, const G3D::Color4& smallGridColor,
const G3D::Color4& largeGridColor)
{
const Vector3 cameraPos = camera.getCameraCoordinateFrame().translation;
const Vector3 zeroPlaneCenter = G3D::Plane(Vector3(0, 1, 0), Vector3(0, yLevel, 0)).closestPoint(cameraPos);
const int x = (int)(Math::iRound((zeroPlaneCenter.x + studsPerBox / 2.0f) / studsPerBox) * studsPerBox);
const int z = (int)(Math::iRound((zeroPlaneCenter.z + studsPerBox / 2.0f) / studsPerBox) * studsPerBox);
const Vector3 zeroPlaneCenterOnGrid = Vector3(x, zeroPlaneCenter.y, z);
const float distFromPlane = (cameraPos - zeroPlaneCenter).magnitude();
const float lineThickness = 2;
const int largeGridStep = studsPerBox * ZEROPLANE_GRID_FACTOR;
const int lineSegmentStep = largeGridStep;
adorn->setObjectToWorldMatrix(Vector3::zero());
// if we are close enough to the zero plane, lets render the smaller grid
if (distFromPlane < ZEROPLANE_GRID_SIZE_BASE)
{
const float zEnd = ZEROPLANE_GRID_SIZE_BASE + zeroPlaneCenter.z;
const float zStart = -ZEROPLANE_GRID_SIZE_BASE + zeroPlaneCenter.z;
const float xEnd = ZEROPLANE_GRID_SIZE_BASE + zeroPlaneCenter.x;
const float xStart = -ZEROPLANE_GRID_SIZE_BASE + zeroPlaneCenter.x;
const int zeroPlaneSmallCenterX = Math::iRound(zeroPlaneCenterOnGrid.x / studsPerBox) * studsPerBox;
const int zeroPlaneSmallCenterZ = Math::iRound(zeroPlaneCenterOnGrid.z / studsPerBox) * studsPerBox;
for (int i = -ZEROPLANE_GRID_SIZE_BASE + zeroPlaneSmallCenterX; i < (ZEROPLANE_GRID_SIZE_BASE + zeroPlaneSmallCenterX); i += studsPerBox)
{
float lastStep = zStart;
for (int j = zStart + lineSegmentStep; j <= zEnd + lineSegmentStep; j += lineSegmentStep)
{
const float cameraToPlaneDistance = (Vector3(i, zeroPlaneCenterOnGrid.y, j + (j - lastStep) / 2.0f) - cameraPos).magnitude();
const float alpha = std::max(1.0f - (cameraToPlaneDistance / ZEROPLANE_GRID_SIZE_BASE), 0.0f);
if (alpha > 0.0f)
{
if (FFlag::Studio3DGridUseAALines)
adorn->line3dAA(Vector3(i, zeroPlaneCenterOnGrid.y, lastStep), Vector3(i, zeroPlaneCenterOnGrid.y, j),
Aya::Color4(smallGridColor.rgb(), alpha), lineThickness, 0, false);
else
adorn->line3d(Vector3(i, zeroPlaneCenterOnGrid.y, lastStep), Vector3(i, zeroPlaneCenterOnGrid.y, j),
Aya::Color4(smallGridColor.rgb(), alpha));
}
lastStep = j;
}
}
for (int i = -ZEROPLANE_GRID_SIZE_BASE + zeroPlaneSmallCenterZ; i < (ZEROPLANE_GRID_SIZE_BASE + zeroPlaneSmallCenterZ); i += studsPerBox)
{
float lastStep = xStart;
for (int j = xStart + lineSegmentStep; j <= xEnd + lineSegmentStep; j += lineSegmentStep)
{
const float cameraToPlaneDistance = (Vector3(j + (j - lastStep) / 2.0f, zeroPlaneCenterOnGrid.y, i) - cameraPos).magnitude();
const float alpha = std::max(1.0f - (cameraToPlaneDistance / ZEROPLANE_GRID_SIZE_BASE), 0.0f);
if (alpha > 0.0f)
{
if (FFlag::Studio3DGridUseAALines)
adorn->line3dAA(Vector3(lastStep, zeroPlaneCenterOnGrid.y, i), Vector3(j, zeroPlaneCenterOnGrid.y, i),
Aya::Color4(smallGridColor.rgb(), alpha), lineThickness, 0, false);
else
adorn->line3d(Vector3(lastStep, zeroPlaneCenterOnGrid.y, i), Vector3(j, zeroPlaneCenterOnGrid.y, i),
Aya::Color4(smallGridColor.rgb(), alpha));
}
lastStep = j;
}
}
}
// now render the large grid
const int zeroPlaneLargeCenterX = Math::iRound(zeroPlaneCenterOnGrid.x / largeGridStep) * largeGridStep;
const int zeroPlaneLargeCenterZ = Math::iRound(zeroPlaneCenterOnGrid.z / largeGridStep) * largeGridStep;
for (int i = -ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterX; i <= (ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterX); i += largeGridStep)
{
const Vector3 lineStart = Vector3(i, zeroPlaneCenterOnGrid.y, -ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterZ);
const Vector3 lineEnd = Vector3(i, zeroPlaneCenterOnGrid.y, ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterZ);
const Vector3 midPoint = lineEnd + ((lineEnd - lineStart) * 0.5f);
const float distFromPlane = (cameraPos - midPoint).magnitude();
const float largeGridAlpha = std::max(1.0f - ((distFromPlane * 1.5f) / (ZEROPLANE_GRID_SIZE_BASE)), 0.25f);
if (FFlag::Studio3DGridUseAALines)
adorn->line3dAA(lineStart, lineEnd, Aya::Color4(largeGridColor.rgb(), largeGridAlpha), lineThickness, 0, false);
else
adorn->line3d(lineStart, lineEnd, Aya::Color4(largeGridColor.rgb(), largeGridAlpha));
}
for (int i = -ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterZ; i <= (ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterZ); i += largeGridStep)
{
const Vector3 lineStart = Vector3(i, zeroPlaneCenterOnGrid.y, -ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterZ);
const Vector3 lineEnd = Vector3(i, zeroPlaneCenterOnGrid.y, ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterZ);
const Vector3 midPoint = lineEnd + ((lineEnd - lineStart) * 0.5f);
const float distFromPlane = (cameraPos - midPoint).magnitude();
const float largeGridAlpha = std::max(1.0f - ((distFromPlane * 1.5f) / (ZEROPLANE_GRID_SIZE_BASE)), 0.25f);
if (FFlag::Studio3DGridUseAALines)
adorn->line3dAA(Vector3(-ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterX, zeroPlaneCenterOnGrid.y, i),
Vector3(ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterX, zeroPlaneCenterOnGrid.y, i),
Aya::Color4(largeGridColor.rgb(), largeGridAlpha), lineThickness, 0, false);
else
adorn->line3d(Vector3(-ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterX, zeroPlaneCenterOnGrid.y, i),
Vector3(ZEROPLANE_GRID_SIZE_BASE + zeroPlaneLargeCenterX, zeroPlaneCenterOnGrid.y, i),
Aya::Color4(largeGridColor.rgb(), largeGridAlpha));
}
// last, we render the axes rays at the origin
static const float ArrowSize = 4.0f;
if (camera.frustum().intersectsSphere(Vector3::zero(), ArrowSize))
{
adorn->setMaterial(Adorn::Material_SelfLitHighlight);
adorn->setObjectToWorldMatrix(CoordinateFrame(Vector3(0, 0, 0)));
adorn->ray(Aya::RbxRay(Vector3(0, 0, 0), Vector3(ArrowSize, 0, 0)), axisColors[0]);
adorn->ray(Aya::RbxRay(Vector3(0, 0, 0), Vector3(0, ArrowSize, 0)), axisColors[1]);
adorn->ray(Aya::RbxRay(Vector3(0, 0, 0), Vector3(0, 0, ArrowSize)), axisColors[2]);
adorn->setMaterial(Adorn::Material_Default);
}
}
void DrawAdorn::surfaceGridAtCoord(Adorn* adorn, CoordinateFrame& cF, const Vector4& bounds, const Vector3& gridXDir, const Vector3& gridYDir,
const G3D::Color4& color, int boxesPerStud)
{
int direction1 = 1;
int direction2 = 0;
int xMaxInt = Math::iRound(bounds.x + 1);
int yMaxInt = Math::iRound(bounds.y + 1);
int xMinInt = Math::iRound(bounds.z - 1);
int yMinInt = Math::iRound(bounds.w - 1);
Matrix3 planeCoordRot =
Math::fromDirectionCosines(gridXDir, gridYDir, gridXDir.cross(gridYDir), Vector3::unitX(), Vector3::unitY(), Vector3::unitZ());
CoordinateFrame planeCoord = cF;
planeCoord.rotation *= planeCoordRot;
CoordinateFrame adornCoord = planeCoord;
Vector3 ptXDir = Vector3::zero();
ptXDir.y = 0.5f * (yMaxInt + yMinInt);
for (int i = xMinInt; i <= xMaxInt; i++)
{
if (boxesPerStud == 0 && i > xMinInt)
i = xMaxInt;
ptXDir.x = (float)i;
Vector3 ptInWorld = planeCoord.pointToWorldSpace(ptXDir);
adornCoord.translation = ptInWorld;
cylinder(adorn, adornCoord, direction1, (float)(yMaxInt - yMinInt), 0.03, color);
if (i < xMaxInt)
{
for (int j = 1; j < boxesPerStud; j++)
{
ptXDir.x = (float)i + (1.0f / (float)boxesPerStud) * (float)j;
ptInWorld = planeCoord.pointToWorldSpace(ptXDir);
adornCoord.translation = ptInWorld;
cylinder(adorn, adornCoord, direction1, (float)(yMaxInt - yMinInt), 0.01, color);
}
}
}
Vector3 ptYDir = Vector3::zero();
ptYDir.x = 0.5f * (xMaxInt + xMinInt);
for (int i = yMinInt; i <= yMaxInt; i++)
{
if (boxesPerStud == 0 && i > yMinInt)
i = yMaxInt;
ptYDir.y = (float)i;
Vector3 ptInWorld = planeCoord.pointToWorldSpace(ptYDir);
adornCoord.translation = ptInWorld;
cylinder(adorn, adornCoord, direction2, (float)(xMaxInt - xMinInt), 0.03, color);
if (i < yMaxInt)
{
for (int j = 1; j < boxesPerStud; j++)
{
ptYDir.y = (float)i + (1.0f / (float)boxesPerStud) * (float)j;
ptInWorld = planeCoord.pointToWorldSpace(ptYDir);
adornCoord.translation = ptInWorld;
cylinder(adorn, adornCoord, direction2, (float)(xMaxInt - xMinInt), 0.01, color);
}
}
}
}
void DrawAdorn::axisWidget(Adorn* adorn, const Aya::Camera& camera)
{
Rect2D viewport = adorn->getViewport();
float sizeOfWidget = 0.1f;
Vector3 offset = Vector3((viewport.width() / 2) - 50, (-viewport.height() / 2) + 50, 0);
Vector3 widgetOrigin3dPos = camera.getRenderingCoordinateFrame().translation + (camera.getRenderingCoordinateFrame().lookVector() * 2);
Vector3 widgetOrigin2dPos = camera.project(widgetOrigin3dPos) - offset;
Vector3 widgetXPos = camera.project(widgetOrigin3dPos + Vector3::unitX() * sizeOfWidget) - offset;
adorn->line2d(Vector2(widgetOrigin2dPos.x, widgetOrigin2dPos.y), Vector2(widgetXPos.x, widgetXPos.y), axisColors[0]);
adorn->drawFont2D("X", Vector2(widgetXPos.x, widgetXPos.y), 12.0f, false, axisColors[0], Aya::Color4::clear(), Aya::Text::FONT_ARIAL);
Vector3 widgetYPos = camera.project(widgetOrigin3dPos + Vector3::unitY() * sizeOfWidget) - offset;
adorn->line2d(Vector2(widgetOrigin2dPos.x, widgetOrigin2dPos.y), Vector2(widgetYPos.x, widgetYPos.y), axisColors[1]);
adorn->drawFont2D("Y", Vector2(widgetYPos.x + 3.0f, widgetYPos.y), 12.0f, false, axisColors[1], Aya::Color4::clear(), Aya::Text::FONT_ARIAL);
Vector3 widgetZPos = camera.project(widgetOrigin3dPos + Vector3::unitZ() * sizeOfWidget) - offset;
adorn->line2d(Vector2(widgetOrigin2dPos.x, widgetOrigin2dPos.y), Vector2(widgetZPos.x, widgetZPos.y), axisColors[2]);
adorn->drawFont2D("Z", Vector2(widgetZPos.x, widgetZPos.y), 12.0f, false, axisColors[2], Aya::Color4::clear(), Aya::Text::FONT_ARIAL);
}
void DrawAdorn::circularGridAtCoord(Adorn* adorn, const CoordinateFrame& coordFrame, const G3D::Vector3& size, const G3D::Vector3& cameraPos,
NormalId normalId, const Color4& color, int boxesPerStud)
{
const Vector3 halfSize = size / 2;
const Extents localExtents = Extents(-halfSize, halfSize);
const Vector3 handlePos = handlePosInObject(cameraPos, localExtents, HANDLE_ROTATE, normalId);
const Vector3 handlePosInWorld = coordFrame.pointToWorldSpace(handlePos);
const Vector3 delta = coordFrame.translation - handlePosInWorld;
const float radius = delta.length();
const Vector3 normal = normalIdToVector3(normalId);
const int direction = (normalId == NORM_X || normalId == NORM_X_NEG) ? 1 : 0;
const float rotationAngle = Math::pif() / boxesPerStud;
const Matrix3 rotation_increment = Matrix3::fromAxisAngle(normal, rotationAngle);
const float line_thickness = scaleRelativeToCamera(cameraPos, coordFrame.translation, RotationGridLineMinAngle, RotationGridLineThickness);
adorn->setMaterial(Adorn::Material_SelfLit);
CoordinateFrame frame = coordFrame;
for (int i = 0; i < boxesPerStud; ++i)
{
frame.rotation *= rotation_increment;
cylinder(adorn, frame, direction, radius * 2, line_thickness, color);
}
adorn->setMaterial(Adorn::Material_Default);
}
void DrawAdorn::lineSegmentRelativeToCoord(
Adorn* adorn, const CoordinateFrame& cF, const Vector3& pt0, const Vector3& pt1, const G3D::Color3& color, float lineThickness)
{
CoordinateFrame lineCf;
Vector3 lineVector = pt1 - pt0;
lineCf.translation = pt0 + lineVector * 0.5f;
float lineLength = lineVector.magnitude();
lineCf.rotation = Math::getWellFormedRotForZVector(lineVector / lineLength);
DrawAdorn::cylinder(adorn, cF * lineCf, 2, lineLength, lineThickness, color);
}
void DrawAdorn::verticalLineSegmentSplit(Adorn* adorn, const CoordinateFrame& cF, const Vector3& pt0, const Vector3& pt1, const float& delta,
const float& dropFactor, const short& level, const G3D::Color3& color, float lineThickness)
{
short maxLevels = 3;
float recursiveDropFactor = 0.25f;
Vector3 splitPoint = 0.5 * (pt1 - pt0) + pt0;
splitPoint.y -= dropFactor * delta;
if (level < maxLevels)
{
verticalLineSegmentSplit(adorn, cF, pt0, splitPoint, delta, recursiveDropFactor * dropFactor, level + 1, color, lineThickness);
verticalLineSegmentSplit(adorn, cF, splitPoint, pt1, delta, recursiveDropFactor * dropFactor, level + 1, color, lineThickness);
}
else
{
lineSegmentRelativeToCoord(adorn, cF, pt0, splitPoint, color, lineThickness);
lineSegmentRelativeToCoord(adorn, cF, pt1, splitPoint, color, lineThickness);
}
}
void DrawAdorn::surfacePolygon(Adorn* adorn, PartInstance& partInst, int surfaceId, const G3D::Color3& color, float lineThickness)
{
std::vector<Vector3> polygonInPart;
for (int i = 0; i < PartInstance::getConstPrimitive(&partInst)->getConstGeometry()->getNumVertsInSurface(surfaceId); i++)
polygonInPart.push_back(PartInstance::getConstPrimitive(&partInst)->getConstGeometry()->getSurfaceVertInBody(surfaceId, i));
DrawAdorn::polygonRelativeToCoord(adorn, PartInstance::getConstPrimitive(&partInst)->getCoordinateFrame(), polygonInPart, color, lineThickness);
}
void DrawAdorn::polygonRelativeToCoord(
Adorn* adorn, const CoordinateFrame& cF, std::vector<Vector3>& vertices, const G3D::Color4& color, float lineThickness)
{
CoordinateFrame lineCf;
for (unsigned int i = 0; i < vertices.size(); i++)
{
int next = i < vertices.size() - 1 ? i + 1 : 0;
Vector3 lineVector = vertices[next] - vertices[i];
lineCf.translation = vertices[i] + lineVector * 0.5f;
float lineLength = lineVector.magnitude();
lineCf.rotation = Math::getWellFormedRotForZVector(lineVector / lineLength);
cylinder(adorn, cF * lineCf, 2, lineLength, lineThickness, color);
}
}
void DrawAdorn::partSurface(const Part& part, int surfaceId, Adorn* adorn, const Color4& color, float thickness)
{
adorn->setObjectToWorldMatrix(part.coordinateFrame);
Vector3 halfRealSize = 0.5f * part.gridSize;
surfaceBorder(adorn, halfRealSize, thickness, surfaceId, color);
}
Vector3 DrawAdorn::handlePosInObject(const Vector3& cameraPos, const Extents& localExtents, HandleType handleType, NormalId normalId)
{
const Vector3 size = localExtents.size();
const Vector3 halfSize = size / 2;
switch (handleType)
{
case HANDLE_ROTATE:
{
const float halfdiagonal = halfSize.length();
const float default_radius = halfdiagonal + HandleOffset * 4;
// TODO fix this so we can have a nice scaled circle far away
// use the default radius because if we scale it's gonna get huge
// need to determine circle size on screen and make sure it's larger
// than the minimum size on screen
const float radius = default_radius;
// const float radius = scaleRelativeToCamera(
// cameraPos,
// localExtents.center(),
// TorusMinAngle,
// default_radius );
const Vector3 axis = normalIdToVector3(normalIdToU(normalId));
return localExtents.center() + axis * radius;
}
case HANDLE_RESIZE:
{
const Vector3 axis = normalIdToVector3(normalId);
const Vector3 start_offset(HandleOffset, HandleOffset, HandleOffset);
return localExtents.center() + axis * (halfSize + start_offset * 4);
}
case HANDLE_VELOCITY:
case HANDLE_MOVE:
{
const Vector3 axis = normalIdToVector3(normalId);
const Vector3 start_offset(HandleOffset, HandleOffset, HandleOffset);
return localExtents.center() + axis * (halfSize + start_offset);
}
default:
AYAASSERT(false);
return Vector3::zero();
}
}
float DrawAdorn::scaleHandleRelativeToCamera(const Vector3& cameraPos, HandleType handleType, const Vector3& handlePos)
{
float min_angle;
float expected_radius;
switch (handleType)
{
case HANDLE_RESIZE:
min_angle = SphereMinAngle;
expected_radius = SphereRadius;
break;
case HANDLE_MOVE:
min_angle = ArrowMinAngle;
expected_radius = ArrowLength;
break;
case HANDLE_ROTATE:
min_angle = SphereMinAngle;
expected_radius = SphereRadius;
break;
case HANDLE_VELOCITY:
min_angle = SphereMinAngle;
expected_radius = SphereRadius;
break;
}
return scaleRelativeToCamera(cameraPos, handlePos, min_angle, expected_radius);
}
// visual angle = radius/distance
float DrawAdorn::scaleRelativeToCamera(const Vector3& cameraPos, const Vector3& handlePos, float minAngle, float expectedSize)
{
const float distance = (cameraPos - handlePos).magnitude();
float scale = 1.0f;
if (distance > (1.0f / minAngle))
scale = distance * minAngle;
const float radius = scale * expectedSize;
return radius;
}
void DrawAdorn::handles2d(const G3D::Vector3& size, const G3D::CoordinateFrame& position, const Aya::Camera& camera, Adorn* adorn,
HandleType handleType, const G3D::Color4& color, bool useAxisColor, int normalIdMask)
{
Vector3 halfSize = size / 2;
Aya::Extents localExtents(-halfSize, halfSize);
for (int posNeg = 0; posNeg < 2; ++posNeg)
{
for (int xyz = 0; xyz < 3; ++xyz)
{
if (normalIdMask & (1 << (xyz + (posNeg * 3))))
{
NormalId normalId = (NormalId)(posNeg * 3 + xyz);
Vector3 handlePos = handlePosInObject(camera.coordinateFrame().translation, localExtents, handleType, normalId);
Vector3 handlePosInWorld = position.pointToWorldSpace(handlePos);
if (handleType == HANDLE_MOVE)
{
// compute relative size on screen based on distance
const float handleRadius =
scaleRelativeToCamera(camera.coordinateFrame().translation, handlePosInWorld, ArrowMinAngle, ArrowLength - 0.5f);
Vector3 correctedHandlePos = normalIdToVector3(normalId) * handleRadius;
// transform point back to world
CoordinateFrame frame(position.rotation, handlePosInWorld);
handlePosInWorld = frame.pointToWorldSpace(correctedHandlePos);
}
Vector3 screenPos = camera.project(handlePosInWorld);
if (screenPos.z == std::numeric_limits<float>::infinity())
return;
adorn->rect2d(Rect::fromCenterSize(screenPos.xy(), 6.0f).toRect2D(), useAxisColor ? axisColors[xyz] : color);
}
}
}
}
void DrawAdorn::partInfoText2D(const G3D::Vector3& size, const G3D::CoordinateFrame& position, const Aya::Camera& camera, Adorn* adorn,
const std::string& text, const G3D::Color4& color, float fontSize, int normalIdMask)
{
Vector3 halfSize = size / 2;
Aya::Extents localExtents(-halfSize, halfSize);
const Vector3 max = localExtents.max();
const Vector3 min = localExtents.min();
Vector3 points[8];
points[0] = Vector3(min.x, min.y, min.z);
points[1] = Vector3(min.x, max.y, min.z);
points[2] = Vector3(min.x, max.y, max.z);
points[3] = Vector3(min.x, min.y, max.z);
points[4] = Vector3(max.x, min.y, min.z);
points[5] = Vector3(max.x, max.y, min.z);
points[6] = Vector3(max.x, max.y, max.z);
points[7] = Vector3(max.x, min.y, max.z);
Vector2 lowestRightCornerMax = Vector2(0.0f, 0.0f);
float minDistanceToOptimal = FLT_MAX;
Vector2 lowestRightCorner;
// In projection space, find the optimal point, the lowest rightest maximum of all the points.
for (int i = 0; i < 8; i++)
{
const Vector3 worldPos = position.pointToWorldSpace(points[i]);
const Vector3 projPos = camera.project(worldPos);
points[i] = projPos;
if (projPos.z == std::numeric_limits<float>::infinity())
continue;
if (projPos.y > lowestRightCornerMax.y)
{
lowestRightCornerMax.y = projPos.y;
}
if (projPos.x > lowestRightCornerMax.x)
{
lowestRightCornerMax.x = projPos.x;
}
}
// Find the point closest to lowest rightest maximum.
for (int i = 0; i < 8; i++)
{
const Vector3 projPos = points[i];
if (projPos.z == std::numeric_limits<float>::infinity())
continue;
float distanceToOptimal = (projPos.xy() - lowestRightCornerMax).length();
if (minDistanceToOptimal > distanceToOptimal)
{
minDistanceToOptimal = distanceToOptimal;
lowestRightCorner = projPos.xy();
}
}
adorn->drawFont2D(text, lowestRightCorner, fontSize, false, color, Aya::Color4::clear(), Aya::Text::FONT_ARIAL);
}
void DrawAdorn::handles3d(const G3D::Vector3& size, const G3D::CoordinateFrame& position, Adorn* adorn, HandleType handleType,
const Vector3& cameraPos, const G3D::Color4& color, bool useAxisColor, int normalIdMask, NormalId normalIdTohighlight,
const G3D::Color4& highlightColor)
{
const Vector3 halfSize = size / 2;
const Extents localExtents(-halfSize, halfSize);
int usedaxes = 0; // for rotation handles, prevent use of NORM_X and NORM_X_NEG at the same time.
for (int posNeg = 0; posNeg < 2; ++posNeg)
{
for (int xyz = 0; xyz < 3; ++xyz)
{
if (normalIdMask & (1 << (xyz + (posNeg * 3))))
{
NormalId normalId = intToNormalId(posNeg * 3 + xyz);
// determine where the handle should be located
const Vector3 handlePos = handlePosInObject(cameraPos, localExtents, handleType, normalId);
const Vector3 handlePosInWorld = position.pointToWorldSpace(handlePos);
// compute relative size on screen based on distance
const float handleRadius = scaleHandleRelativeToCamera(cameraPos, handleType, handlePosInWorld);
const CoordinateFrame frame(position.rotation, handlePosInWorld);
adorn->setObjectToWorldMatrix(frame);
if (normalIdTohighlight == normalId)
adorn->setMaterial(Adorn::Material_SelfLitHighlight);
else
adorn->setMaterial(Adorn::Material_SelfLit);
const int axis_index = normalId % 3;
Color4 handleColor = useAxisColor ? axisColors[axis_index] : color;
if (normalIdTohighlight != normalId)
handleColor.a *= HandleTransparency;
switch (handleType)
{
case HANDLE_RESIZE:
case HANDLE_VELOCITY:
{
if (normalIdTohighlight == normalId)
adorn->sphere(Sphere(Vector3::zero(), handleRadius + 0.08), handleColor);
else
adorn->sphere(Sphere(Vector3::zero(), handleRadius), handleColor);
break;
}
case HANDLE_MOVE:
{
Vector3 direction = normalIdToVector3(normalId) * handleRadius;
RbxRay rayInPart = RbxRay::fromOriginAndDirection(Vector3::zero(), direction);
adorn->ray(rayInPart, handleColor);
break;
}
case HANDLE_ROTATE:
{
const int axis_flag = normalIdToMask(normalId);
// only allow if positive orientation wasn't already specified.
if ((usedaxes & axis_flag) == 0)
{
const Vector3 delta = position.translation - handlePosInWorld;
const float torus_radius = delta.length();
// compute relative size thickness based on distance
const float thickness = scaleRelativeToCamera(cameraPos, position.translation, TorusThicknessMinAngle, TorusThickness);
Color4 axisColor(handleColor);
if (normalIdTohighlight != normalId)
axisColor.a = AxisTransparency;
torus(adorn, position, normalId, torus_radius, thickness, axisColor);
usedaxes |= axis_flag;
}
// need to rest the matrix because torus changed it
adorn->setObjectToWorldMatrix(handlePosInWorld);
if (normalIdTohighlight == normalId)
adorn->sphere(Sphere(Vector3::zero(), handleRadius + 0.08), handleColor);
else
adorn->sphere(
Sphere(Vector3::zero(), handleRadius), Color3(handleColor.r - 0.02, handleColor.g - 0.02, handleColor.b - 0.02));
break;
}
default:
AYAASSERT(false);
break;
}
}
}
}
adorn->setMaterial(Adorn::Material_Default);
}
// circle, with normal of linespace parallel to radius.
class CircleRadialNormal : public I3DLinearFunc
{
Vector3 x;
Vector3 y;
Vector3 z;
float r;
NormalId axis;
public:
CircleRadialNormal(float radius, NormalId axis)
: r(radius)
, axis(axis)
{
x = uvwToObject(Vector3::unitX(), axis);
y = uvwToObject(Vector3::unitY(), axis);
z = uvwToObject(Vector3::unitZ(), axis);
}
Vector3 eval(float t)
{
double theta = t * 2 * G3D::pi();
return x * (float)(cos(theta) * r) + y * (float)(sin(theta) * r);
}
// first derivative.
Vector3 evalTangent(float t)
{
double theta = t * 2 * G3D::pi();
return y * (float)cos(theta) - x * (float)sin(theta);
}
Vector3 evalNormal(float t)
{
double theta = t * 2 * G3D::pi();
return x * (float)cos(theta) + y * (float)sin(theta);
}
Vector3 evalBinormal(float t)
{
return -z;
}
// string that encodes this function in a unique way.
std::string hashString()
{
std::ostringstream hash;
hash << "CircleRN(" << r << "," << axis << ")";
return hash.str();
}
};
void DrawAdorn::chatBubble2d(Adorn* adorn, const Rect2D& rect, const G3D::Vector2 pointer, float cornerradius, const float linewidth,
const int quarterdivs, const Color4& color)
{
cornerradius = std::min(cornerradius, std::min(rect.width(), rect.height()));
const float pointerwidth = std::min(cornerradius * 1.5f, (rect.width() - cornerradius * 2.0f));
std::vector<G3D::Vector2> verts;
std::vector<G3D::Vector2> vertsBorder;
// start at base, right side of pointer.
// go counterclockwise.
// ui space: math space:
// (0,0)
// /--------------\ n
// | | \
// | | n-1\ \ 0 1
// \-----/ /-----/ /------\ \-----\
// n-1/ / 0 1 | |
// / | |
// n \---------------/
// (0,0)
// y1 should be bottom.
Vector2 p0(rect.center().x + pointerwidth / 2, rect.y1());
Vector2 p1(rect.center().x - pointerwidth / 2, rect.y1());
verts.push_back(p0);
vertsBorder.push_back(p0 + Vector2(linewidth, linewidth));
AYAASSERT(rect.wh().x >= cornerradius * 2);
AYAASSERT(rect.wh().y >= cornerradius * 2);
Rect2D innerrect = rect.border(cornerradius);
double angleinc = (quarterdivs > 0) ? (-(Math::pi() * 0.5) / quarterdivs) : 0;
for (int i = 0; i <= quarterdivs; ++i)
{
double angle = (quarterdivs > 0) ? (Math::pi() * 0.5 + angleinc * i) : (Math::pi() * 0.25);
Vector2 r = Vector2((float)cos(angle), (quarterdivs > 0) ? (float)sin(angle) : 1.0f);
verts.push_back(innerrect.x1y1() + r * cornerradius);
vertsBorder.push_back(innerrect.x1y1() + r * (cornerradius + linewidth));
}
for (int i = 0; i <= quarterdivs; ++i)
{
double angle = (quarterdivs > 0) ? (Math::pi() * 0.0 + angleinc * i) : (Math::pi() * 1.75);
Vector2 r = Vector2((float)cos(angle), (float)sin(angle));
verts.push_back(innerrect.x1y0() + r * cornerradius);
vertsBorder.push_back(innerrect.x1y0() + r * (cornerradius + linewidth));
}
for (int i = 0; i <= quarterdivs; ++i)
{
double angle = (quarterdivs > 0) ? (Math::pi() * 1.5 + angleinc * i) : (Math::pi() * 1.25);
Vector2 r = Vector2((float)cos(angle), (float)sin(angle));
verts.push_back(innerrect.x0y0() + r * cornerradius);
vertsBorder.push_back(innerrect.x0y0() + r * (cornerradius + linewidth));
}
for (int i = 0; i <= quarterdivs; ++i)
{
double angle = (quarterdivs > 0) ? (Math::pi() * 1.0 + angleinc * i) : (Math::pi() * .75);
Vector2 r = Vector2((float)cos(angle), (quarterdivs > 0) ? (float)sin(angle) : 1.0f);
verts.push_back(innerrect.x0y1() + r * cornerradius);
vertsBorder.push_back(innerrect.x0y1() + r * (cornerradius + linewidth));
}
verts.push_back(p1);
vertsBorder.push_back(p1 + Vector2(-linewidth, linewidth));
verts.push_back(pointer);
Vector2 pointerdir = pointer - (p0 + p1) * 0.5f;
pointerdir.unitize();
vertsBorder.push_back(pointer + pointerdir * linewidth * 3); // todo: not correct.
adorn->convexPolygon2d(&vertsBorder[0], vertsBorder.size(), G3D::Color4(Color3::black()));
adorn->convexPolygon2d(&verts[0], verts.size(), G3D::Color4(color));
}
void DrawAdorn::torus(Adorn* adorn, const CoordinateFrame& position, NormalId axis, float radius, float thicknessradius, const Color4& color)
{
CircleRadialNormal trajectory(radius, axis);
CircleRadialNormal profile(thicknessradius, NORM_Z);
adorn->setObjectToWorldMatrix(position);
adorn->extrusion(&trajectory, 32, &profile, 8, color);
}
/**
* Draws a wire star made up of 3 line segments.
*
* @param adorn object used to generate the geometry
* @param center world space center coordinates
* @param size lines are -size to +size on the axis
* @param colorX color of x axis line
* @param colorY color of y axis line
* @param colorZ color of z axis line
*/
void DrawAdorn::star(Adorn* adorn, const Vector3& center, float size, const Color4& colorX, const Color4& colorY, const Color4& colorZ)
{
size /= 2.0f;
adorn->line3d(center - Vector3::unitX() * size, center + Vector3::unitX() * size, colorX);
adorn->line3d(center - Vector3::unitY() * size, center + Vector3::unitY() * size, colorY);
adorn->line3d(center - Vector3::unitZ() * size, center + Vector3::unitZ() * size, colorZ);
}
/**
* Draw an outline (wireframe) box in 3D.
*
* @param adorn adornment to use for drawing
* @param box box to draw
* @param color color of lines
*/
void DrawAdorn::outlineBox(Adorn* adorn, const AABox& box, const Color4& color)
{
const Vector3& p0 = box.low();
const Vector3& p1 = box.high();
const float X0 = p0.x;
const float Y0 = p0.y;
const float Z0 = p0.z;
const float X1 = p1.x;
const float Y1 = p1.y;
const float Z1 = p1.z;
adorn->line3d(Vector3(X0, Y0, Z0), Vector3(X1, Y0, Z0), color);
adorn->line3d(Vector3(X0, Y1, Z0), Vector3(X1, Y1, Z0), color);
adorn->line3d(Vector3(X0, Y0, Z1), Vector3(X1, Y0, Z1), color);
adorn->line3d(Vector3(X0, Y1, Z1), Vector3(X1, Y1, Z1), color);
adorn->line3d(Vector3(X0, Y0, Z0), Vector3(X0, Y1, Z0), color);
adorn->line3d(Vector3(X1, Y0, Z0), Vector3(X1, Y1, Z0), color);
adorn->line3d(Vector3(X0, Y0, Z1), Vector3(X0, Y1, Z1), color);
adorn->line3d(Vector3(X1, Y0, Z1), Vector3(X1, Y1, Z1), color);
adorn->line3d(Vector3(X0, Y0, Z0), Vector3(X0, Y0, Z1), color);
adorn->line3d(Vector3(X1, Y0, Z0), Vector3(X1, Y0, Z1), color);
adorn->line3d(Vector3(X0, Y1, Z0), Vector3(X0, Y1, Z1), color);
adorn->line3d(Vector3(X1, Y1, Z0), Vector3(X1, Y1, Z1), color);
}
/**
* Draw select indicator around a box similar to 3DS.
*
* @param adorn adornment to use for drawing
* @param box box to draw
* @param color color of lines
*/
void DrawAdorn::selectionBox(Adorn* adorn, const AABox& box, const Color4& color)
{
const Vector3& P0 = box.low();
const Vector3& P1 = box.high();
float X0 = P0.x;
float Y0 = P0.y;
float Z0 = P0.z;
float X1 = P1.x;
float Y1 = P1.y;
float Z1 = P1.z;
float s0 = X0 + (X1 - X0) / 4;
float s1 = X1 + (X0 - X1) / 4;
float s2 = Y0 + (Y1 - Y0) / 4;
float s3 = Y1 + (Y0 - Y1) / 4;
float s4 = Z0 + (Z1 - Z0) / 4;
float s5 = Z1 + (Z0 - Z1) / 4;
// side0
adorn->line3d(Vector3(X0, Y0, Z0), Vector3(s0, Y0, Z0), color);
adorn->line3d(Vector3(X0, Y1, Z0), Vector3(s0, Y1, Z0), color);
adorn->line3d(Vector3(X0, Y0, Z1), Vector3(s0, Y0, Z1), color);
adorn->line3d(Vector3(X0, Y1, Z1), Vector3(s0, Y1, Z1), color);
// side1
adorn->line3d(Vector3(X1, Y0, Z0), Vector3(s1, Y0, Z0), color);
adorn->line3d(Vector3(X1, Y1, Z0), Vector3(s1, Y1, Z0), color);
adorn->line3d(Vector3(X1, Y0, Z1), Vector3(s1, Y0, Z1), color);
adorn->line3d(Vector3(X1, Y1, Z1), Vector3(s1, Y1, Z1), color);
// side2
adorn->line3d(Vector3(X0, Y0, Z0), Vector3(X0, s2, Z0), color);
adorn->line3d(Vector3(X1, Y0, Z0), Vector3(X1, s2, Z0), color);
adorn->line3d(Vector3(X0, Y0, Z1), Vector3(X0, s2, Z1), color);
adorn->line3d(Vector3(X1, Y0, Z1), Vector3(X1, s2, Z1), color);
// side3
adorn->line3d(Vector3(X0, Y1, Z0), Vector3(X0, s3, Z0), color);
adorn->line3d(Vector3(X1, Y1, Z0), Vector3(X1, s3, Z0), color);
adorn->line3d(Vector3(X0, Y1, Z1), Vector3(X0, s3, Z1), color);
adorn->line3d(Vector3(X1, Y1, Z1), Vector3(X1, s3, Z1), color);
// side4
adorn->line3d(Vector3(X0, Y0, Z0), Vector3(X0, Y0, s4), color);
adorn->line3d(Vector3(X1, Y0, Z0), Vector3(X1, Y0, s4), color);
adorn->line3d(Vector3(X0, Y1, Z0), Vector3(X0, Y1, s4), color);
adorn->line3d(Vector3(X1, Y1, Z0), Vector3(X1, Y1, s4), color);
// side5
adorn->line3d(Vector3(X0, Y0, Z1), Vector3(X0, Y0, s5), color);
adorn->line3d(Vector3(X1, Y0, Z1), Vector3(X1, Y0, s5), color);
adorn->line3d(Vector3(X0, Y1, Z1), Vector3(X0, Y1, s5), color);
adorn->line3d(Vector3(X1, Y1, Z1), Vector3(X1, Y1, s5), color);
}
} // namespace Aya

164
engine/3d/src/DrawAdorn.hpp Normal file
View File

@@ -0,0 +1,164 @@
#pragma once
#include "SelectState.hpp"
#include "HandleType.hpp"
#include "Utility/G3DCore.hpp"
#include "Utility/NormalId.hpp"
#include <vector>
#include "World/Primitive.hpp"
#include "DataModel/PartInstance.hpp"
#include "Tool/DragTypes.hpp"
namespace Aya
{
class Part;
class Adorn;
class Face;
class Camera;
class DrawAdorn
{
private:
static void surfaceBorder(Adorn* adorn, const Vector3& halfRealSize, float highlight, int surfaceId, const Color4& color);
public:
static const Color3& resizeColor()
{
static Color3 c(0.1f, 0.6f, 1.0f);
return c;
}
static float scaleHandleRelativeToCamera(const Vector3& cameraPos, HandleType handleType, const Vector3& handlePos);
static Vector3 handlePosInObject(const Vector3& cameraPos, const Extents& localExtents, HandleType handleType, NormalId normalId);
static void cylinder(Adorn* adorn, const CoordinateFrame& worldC, int axis, float length, float radius, const Color4& color, bool cap = true);
static void faceInWorld(Adorn* adorn, const Face& face, float thickness, const Color4& color);
static void partSurface(
const Part& part, int surfaceId, Adorn* adorn, const G3D::Color4& color = G3D::Color4(G3D::Color3::orange()), float thickness = 0.2f);
static void surfaceGridOnFace(const Primitive& prim, Adorn* adorn, int surfaceId, const G3D::Color4& color, int boxesPerStud);
static void zeroPlaneGrid(Adorn* adorn, const Aya::Camera& camera, const int studsPerBox, const int yLevel, const G3D::Color4& smallGridColor,
const G3D::Color4& largeGridColor);
static void surfaceGridAtCoord(Adorn* adorn, CoordinateFrame& cF, const Vector4& bounds, const Vector3& gridXDir, const Vector3& gridYDir,
const G3D::Color4& color, int boxesPerStud);
static void axisWidget(Adorn* adorn, const Aya::Camera& camera);
static void circularGridAtCoord(Adorn* adorn, const CoordinateFrame& coordFrame, const G3D::Vector3& size, const G3D::Vector3& cameraPos,
NormalId normalId, const Color4& color, int boxesPerStud);
static void surfacePolygon(Adorn* adorn, PartInstance& partInst, int surfaceId, const G3D::Color3& color, float lineThickness);
static void polygonRelativeToCoord(
Adorn* adorn, const CoordinateFrame& cF, std::vector<Vector3>& vertices, const G3D::Color4& color, float lineThickness);
static void lineSegmentRelativeToCoord(
Adorn* adorn, const CoordinateFrame& cF, const Vector3& pt0, const Vector3& pt1, const G3D::Color3& color, float lineThickness);
static void verticalLineSegmentSplit(Adorn* adorn, const CoordinateFrame& cF, const Vector3& pt0, const Vector3& pt1, const float& delta,
const float& magicParam, const short& level, const G3D::Color3& color, float lineThickness);
static void handles3d(const G3D::Vector3& size, const G3D::CoordinateFrame& position, Adorn* adorn, HandleType handleType,
const G3D::Vector3& cameraPos, const G3D::Color4& color, bool useAxisColor = true, int normalIdMask = NORM_ALL_MASK,
NormalId normalIdTohighlight = NORM_UNDEFINED, const G3D::Color4& highlightColor = cornflowerblue);
// Must be in 2d Mode
static void handles2d(const G3D::Vector3& size, const G3D::CoordinateFrame& position, const Aya::Camera& camera, Adorn* adorn,
HandleType handleType, const G3D::Color4& color, bool useAxisColor = true, int normalIdMask = NORM_ALL_MASK);
static void partInfoText2D(const G3D::Vector3& size, const G3D::CoordinateFrame& position, const Aya::Camera& camera, Adorn* adorn,
const std::string& text, const G3D::Color4& color, float fontSize = 18.0f, int normalIdMask = NORM_ALL_MASK);
static void chatBubble2d(Adorn* adorn, const Rect2D& rect, const G3D::Vector2 pointer, float cornerradius, const float linewidth,
const int quarterdivs, const Color4& color);
static void torus(Adorn* adorn, const CoordinateFrame& worldC, NormalId axis, float radius, float thicknessradius, const Color4& color);
/**
* Draws a wire star made up of 3 line segments.
*
* @param adorn object used to generate the geometry
* @param center world space center coordinates
* @param size lines are -size to +size on the axis
* @param colorX color of x axis line
* @param colorY color of y axis line
* @param colorZ color of z axis line
*/
static void star(Adorn* adorn, const Vector3& center, float size = 1.0f, const Color4& colorX = Color3::white(),
const Color4& colorY = Color3::white(), const Color4& colorZ = Color3::white());
/**
* Draw an outline (wireframe) box in 3D.
*
* @param adorn adornment to use for drawing
* @param box box to draw
* @param color color of lines
*/
static void outlineBox(Adorn* adorn, const AABox& box, const Color4& color);
/**
* Draw an outline (wireframe) box in 3D.
*
* @param adorn adornment to use for drawing
* @param extents box dimensions
* @param color color of lines
*/
static void outlineBox(Adorn* adorn, const Extents& extents, const Color4& color)
{
AABox aa_box(extents.min(), extents.max());
outlineBox(adorn, aa_box, color);
}
/**
* Draw select indicator around a box similar to 3DS.
*
* @param adorn adornment to use for drawing
* @param box box to draw
* @param color color of lines
*/
static void selectionBox(Adorn* adorn, const AABox& box, const Color4& color);
/**
* Draw select indicator around a box similar to 3DS.
*
* @param adorn adornment to use for drawing
* @param extents box dimensions
* @param color color of lines
*/
static void selectionBox(Adorn* adorn, const Extents& extents, const Color4& color)
{
AABox aa_box(extents.min(), extents.max());
selectionBox(adorn, aa_box, color);
}
// common colors
static const Color3 beige;
static const Color3 darkblue;
static const Color3 powderblue;
static const Color3 skyblue;
static const Color3 violet;
static const Color3 slategray;
static const Color3 aqua;
static const Color3 tan;
static const Color3 wheat;
static const Color3 cornflowerblue;
static const Color3 limegreen;
static const Color3 magenta;
static const Color3 pink;
static const Color3 silver;
// Color values for each axis (x, y, z).
static const Color3 axisColors[3];
private:
static float scaleRelativeToCamera(const Vector3& cameraPos, const Vector3& handlePos, float minAngle, float expectedRadius);
};
} // namespace Aya

View File

@@ -0,0 +1,31 @@
#pragma once
#include "Utility/G3DCore.hpp"
namespace G3D
{
class RenderDevice;
}
namespace Aya
{
class Rect;
class DrawPrimitives
{
public:
static void rawBox(const AABox& box, G3D::RenderDevice* rd);
static void rawSphere(float radius, G3D::RenderDevice* rd);
static void rawCylinderAlongX(float radius, float axis, G3D::RenderDevice* rd, bool cap);
// generic 2d - must be in 2d mode
static void rect2d(const Aya::Rect& rect, G3D::RenderDevice* rd, const G3D::Color4& color = G3D::Color3::white());
static void line2d(const G3D::Vector2& p0, const G3D::Vector2& p1, G3D::RenderDevice* rd, const G3D::Color4& color = G3D::Color3::white());
static void outlineRect2d(const Aya::Rect& rect, float thick, G3D::RenderDevice* rd, const G3D::Color4& color = G3D::Color3::blue());
};
} // namespace Aya

View File

@@ -0,0 +1,28 @@
/**
@file EqualsTrait.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2008-10-01
@edited 2008-10-01
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_EQUALSTRAIT_H
#define G3D_EQUALSTRAIT_H
#include "platform.hpp"
/** Default implementation of EqualsTrait.
@see G3D::Table for specialization requirements.
*/
template<typename Key>
struct EqualsTrait
{
static bool equals(const Key& a, const Key& b)
{
return a == b;
}
};
#endif

152
engine/3d/src/Frustum.cpp Normal file
View File

@@ -0,0 +1,152 @@
#include "Frustum.hpp"
#include "Debug.hpp"
#include "Utility/Math.hpp"
#include "Utility/Extents.hpp"
using G3D::Plane;
using G3D::Vector3;
using namespace Aya;
// creates a frustum in a local coordinate space with the apex at the origin
// translates the frustum into the space defined by apex, dir and up
// performing the floating point operations in a local space helps reduce numerical errors when all of the points are far from the origin
Frustum::Frustum(
const Vector3& apex, const Vector3& dir, const Vector3& up, const float nearDist, const float farDist, const float fovx, const float fovy)
{
Matrix3 rotation;
// up and dir should already be normalized and perpendicular to each other
AYAASSERT(dir.isUnit());
AYAASSERT(up.isUnit());
// due to rounding errors the cross product may not be a unit vector even though it should be.
const Vector3 right = up.cross(dir).unit();
rotation.setColumn(0, right);
rotation.setColumn(1, up);
rotation.setColumn(2, -dir);
// Near plane (wind backwards so normal faces into frustum)
// Recall that nearPlane, farPlane are positive numbers, so
// we need to negate them to produce actual z values.
faceArray.append(Plane(Vector3(0.0f, 0.0f, -1.0f), Vector3(0, 0, -nearDist)));
// Right plane
faceArray.append(Plane(Vector3(-cosf(fovx / 2.0f), 0.0f, -sinf(fovx / 2.0f)), Vector3::zero()));
// Left plane
faceArray.append(Plane(Vector3(-faceArray.last().normal().x, 0.0f, faceArray.last().normal().z), Vector3::zero()));
// Bottom plane
faceArray.append(Plane(Vector3(0.0f, cosf(fovy / 2.0f), -sinf(fovy / 2.0f)), Vector3::zero()));
// Top plane
faceArray.append(Plane(Vector3(0.0f, -faceArray.last().normal().y, faceArray.last().normal().z), Vector3::zero()));
// Far plane
if (farDist < inf())
{
faceArray.append(Plane(Vector3(0.0f, 0.0f, 1.0f), Vector3(0.0f, 0.0f, -farDist)));
}
// Transform planes to world space
for (int face = 0; face < faceArray.size(); ++face)
{
// Since there is no scale factor, we don't have to
// worry about the inverse transpose of the normal.
Vector3 normal;
float d;
faceArray[face].getEquation(normal, d);
Vector3 newNormal = rotation * normal;
if (G3D::isFinite(d))
{
d = (newNormal * -d + apex).dot(newNormal);
faceArray[face] = Plane(newNormal, newNormal * d);
}
else
{
// When d is infinite, we can't multiply 0's by it without
// generating NaNs.
faceArray[face] = Plane::fromEquation(newNormal.x, newNormal.y, newNormal.z, d);
}
}
}
bool Frustum::containsAABB(const Extents& aabb) const
{
for (int ii = 0; ii < 8; ++ii)
{
Vector3 corner = aabb.getCorner(ii);
if (!containsPoint(corner))
{
return false;
}
}
return true;
}
bool Frustum::intersectsAABB(const Aya::Extents& aabb, const G3D::CoordinateFrame& extentsFrame) const
{
Aya::Extents worldBox = aabb.toWorldSpace(extentsFrame);
Vector3 extent = worldBox.size() * 0.5f;
Vector3 center = worldBox.center();
for (int ii = 0; ii < 6; ++ii)
{
const Plane& plane = faceArray[ii];
float d = plane.normal().dot(center) - plane.distance();
float r = Vector3(fabsf(plane.normal().x), fabsf(plane.normal().y), fabsf(plane.normal().z)).dot(extent);
// box is in negative half-space => outside the frustum
if (d + r < 0)
return false;
}
return true;
}
bool Frustum::containsAABB(const Extents& aabb, const G3D::CoordinateFrame& extentsFrame) const
{
for (int ii = 0; ii < 8; ++ii)
{
Vector3 corner = aabb.getCorner(ii);
Vector3 world = extentsFrame.pointToWorldSpace(corner);
if (!containsPoint(world))
{
return false;
}
}
return true;
}
bool Frustum::containsPoint(const Vector3& point) const
{
for (int ii = 0; ii < faceArray.size(); ++ii)
{
const Plane& plane = faceArray[ii];
if (!plane.halfSpaceContains(point))
{
return false;
}
}
return true;
}
bool Frustum::intersectsSphere(const Vector3& center, float radius) const
{
for (int ii = 0; ii < faceArray.size(); ++ii)
{
const Plane& p = faceArray[ii];
Plane offsetplane(p.normal(), p.distance() - radius);
if (!offsetplane.halfSpaceContains(center))
{
return false;
}
}
return true;
}

53
engine/3d/src/Frustum.hpp Normal file
View File

@@ -0,0 +1,53 @@
#ifndef RBXG3D_FRUSTUM_H
#define RBXG3D_FRUSTUM_H
#include "Plane.hpp"
#include "Vector3.hpp"
namespace G3D
{
class CoordinateFrame;
}
namespace Aya
{
class Extents;
class Frustum
{
public:
Frustum(){};
// fovx, fovy are in radians
Frustum(const G3D::Vector3& apex, const G3D::Vector3& dir, const G3D::Vector3& up, float nearDist, float farDist, float fovx, float fovy);
enum FrustumPlane
{
kPlaneNear = 0,
kPlaneRight,
kPlaneLeft,
kPlaneBottom,
kPlaneTop,
kPlaneFar,
kPlaneInvalid,
};
/** The faces in the frustum. When the
far plane is at infinity, there are 5 faces,
otherwise there are 6. The faces are in the order
N,R,L,B,T,[F].
*/
G3D::Array<G3D::Plane> faceArray;
bool containsPoint(const G3D::Vector3& point) const;
bool intersectsSphere(const G3D::Vector3& center, float radius) const;
bool containsAABB(const Aya::Extents& aabb) const;
bool intersectsAABB(const Aya::Extents& aabb, const G3D::CoordinateFrame& extentsFrame) const;
bool containsAABB(const Aya::Extents& aabb, const G3D::CoordinateFrame& extentsFrame) const;
};
} // namespace Aya
#endif

View File

@@ -0,0 +1,68 @@
/**
@file debug.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2001-08-26
@edited 2006-02-16
Copyright 2000-2006, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_DEBUG_H
#define G3D_DEBUG_H
#include "platform.hpp"
#ifdef _MSC_VER
#include <crtdbg.h>
#endif
#include "debugPrintf.hpp"
#include "debugAssert.hpp"
namespace G3D
{
#ifdef _MSC_VER
// Turn off 64-bit warnings
#pragma warning(push)
#pragma warning(disable : 4312)
#pragma warning(disable : 4267)
#pragma warning(disable : 4311)
#endif
/**
Useful for debugging purposes.
*/
inline bool isValidHeapPointer(const void* x)
{
#ifdef _MSC_VER
return (x != (void*)0xcccccccc) && (x != (void*)0xdeadbeef) && (x != (void*)0xfeeefeee);
#else
return x != NULL;
#endif
}
/**
Returns true if the pointer is likely to be
a valid pointer (instead of an arbitrary number).
Useful for debugging purposes.
*/
inline bool isValidPointer(const void* x)
{
#ifdef _MSC_VER
return x != ((void*)0xcccccccc) && (x != (void*)0xdeadbeef) && (x != (void*)0xfeeefeee);
#else
return x != NULL;
#endif
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
} // namespace G3D
#endif

View File

@@ -0,0 +1,58 @@
/**
@file G3DGameUnits.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-10-05
@edited 2006-11-10
*/
#ifndef G3D_GAMEUNITS_H
#define G3D_GAMEUNITS_H
#include "platform.hpp"
namespace G3D
{
/**
Time, in seconds.
*/
typedef double GameTime;
typedef double SimTime;
/**
Actual wall clock time in seconds.
*/
typedef double RealTime;
enum AMPM
{
AM,
PM
};
/** \deprecated */
enum
{
SECOND = 1,
MINUTE = 60,
HOUR = 60 * 60,
DAY = 24 * 60 * 60,
SUNRISE = 24 * 60 * 60 / 4,
SUNSET = 24 * 60 * 60 * 3 / 4,
MIDNIGHT = 0,
METER = 1,
KILOMETER = 1000
};
/**
Converts a 12 hour clock time into the number of seconds since
midnight. Note that 12:00 PM is noon and 12:00 AM is midnight.
Example: <CODE>toSeconds(10, 00, AM)</CODE>
*/
SimTime toSeconds(int hour, int minute, double seconds, AMPM ap);
SimTime toSeconds(int hour, int minute, AMPM ap);
} // namespace G3D
#endif

453
engine/3d/src/GCamera.cpp Normal file
View File

@@ -0,0 +1,453 @@
/**
Aya - We are not using the G3D 8.0 G3D::GCamera, intstead we are using the Aya:Rbx::Camera from old G3D.
@file GCamera.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
@author Jeff Marsceill, 08jcm@williams.edu
@created 2005-07-20
@edited 2010-02-22
*/
#include "GCamera.hpp"
#include "platform.hpp"
#include "Rect2D.hpp"
#include "Ray.hpp"
#include "Matrix4.hpp"
#include "stringutils.hpp"
namespace G3D
{
GCamera::GCamera()
{
debugAssert(1); /// Aya - Do not use, intead use Aya::RbxCamera
setNearPlaneZ(-0.2f);
setFarPlaneZ(-150.0f);
setFieldOfView((float)toRadians(90.0f), HORIZONTAL);
}
GCamera::GCamera(const Matrix4& proj, const CFrame& frame)
{
debugAssert(1); /// Aya - Do not use, intead use Aya::RbxCamera
float left, right, bottom, top, nearval, farval;
proj.getPerspectiveProjectionParameters(left, right, bottom, top, nearval, farval);
setNearPlaneZ(-nearval);
setFarPlaneZ(-farval);
float x = right;
// Assume horizontal field of view
setFieldOfView(atan2(x, -m_nearPlaneZ) * 2.0f, HORIZONTAL);
setCoordinateFrame(frame);
}
GCamera::~GCamera() {}
void GCamera::getCoordinateFrame(CoordinateFrame& c) const
{
c = m_cframe;
}
void GCamera::setCoordinateFrame(const CoordinateFrame& c)
{
m_cframe = c;
}
void GCamera::setFieldOfView(float angle, FOVDirection dir)
{
debugAssert((angle < pi()) && (angle > 0));
m_fieldOfView = angle;
m_direction = dir;
}
float GCamera::imagePlaneDepth() const
{
return -m_nearPlaneZ;
}
float GCamera::viewportWidth(const Rect2D& viewport) const
{
// Compute the side of a square at the near plane based on our field of view
float s = 2.0f * -m_nearPlaneZ * tan(m_fieldOfView * 0.5f);
if (m_direction == VERTICAL)
{
s *= viewport.width() / viewport.height();
}
return s;
}
float GCamera::viewportHeight(const Rect2D& viewport) const
{
// Compute the side of a square at the near plane based on our field of view
float s = 2.0f * -m_nearPlaneZ * tan(m_fieldOfView * 0.5f);
debugAssert(m_fieldOfView < toRadians(180));
if (m_direction == HORIZONTAL)
{
s *= viewport.height() / viewport.width();
}
return s;
}
Ray GCamera::worldRay(float x, float y, const Rect2D& viewport) const
{
int screenWidth = iFloor(viewport.width());
int screenHeight = iFloor(viewport.height());
Vector3 origin = m_cframe.translation;
float cx = screenWidth / 2.0f;
float cy = screenHeight / 2.0f;
float vw = viewportWidth(viewport);
float vh = viewportHeight(viewport);
Vector3 direction = Vector3((x - cx) * vw / screenWidth, -(y - cy) * vh / screenHeight, m_nearPlaneZ);
direction = m_cframe.vectorToWorldSpace(direction);
// Normalize the direction (we didn't do it before)
direction = direction.direction();
return Ray::fromOriginAndDirection(origin, direction);
}
void GCamera::getProjectPixelMatrix(const Rect2D& viewport, Matrix4& P) const
{
getProjectUnitMatrix(viewport, P);
float screenWidth = viewport.width();
float screenHeight = viewport.height();
float sx = screenWidth / 2.0;
float sy = screenHeight / 2.0;
P = Matrix4(sx, 0, 0, sx + viewport.x0() - m_pixelOffset.x, 0, -sy, 0, sy + viewport.y0() + m_pixelOffset.y, 0, 0, 1, 0, 0, 0, 0, 1) * P;
}
void GCamera::getProjectUnitMatrix(const Rect2D& viewport, Matrix4& P) const
{
float screenWidth = viewport.width();
float screenHeight = viewport.height();
float r, l, t, b, n, f, x, y;
float s = 1.0f;
if (m_direction == VERTICAL)
{
y = -m_nearPlaneZ * tan(m_fieldOfView / 2);
x = y * (screenWidth / screenHeight);
s = screenHeight;
}
else
{ // m_direction == HORIZONTAL
x = -m_nearPlaneZ * tan(m_fieldOfView / 2);
y = x * (screenHeight / screenWidth);
s = screenWidth;
}
n = -m_nearPlaneZ;
f = -m_farPlaneZ;
r = x - m_pixelOffset.x / s;
l = -x - m_pixelOffset.x / s;
t = y + m_pixelOffset.y / s;
b = -y + m_pixelOffset.y / s;
P = Matrix4::perspectiveProjection(l, r, b, t, n, f);
}
Vector3 GCamera::projectUnit(const Vector3& point, const Rect2D& viewport) const
{
Matrix4 M;
getProjectUnitMatrix(viewport, M);
Vector4 cameraSpacePoint(coordinateFrame().pointToObjectSpace(point), 1.0f);
const Vector4& screenSpacePoint = M * cameraSpacePoint;
return Vector3(screenSpacePoint.xyz() / screenSpacePoint.w);
}
Vector3 GCamera::project(const Vector3& point, const Rect2D& viewport) const
{
// Find the point in the homogeneous cube
const Vector3& cube = projectUnit(point, viewport);
return convertFromUnitToNormal(cube, viewport);
}
Vector3 GCamera::unprojectUnit(const Vector3& v, const Rect2D& viewport) const
{
const Vector3& projectedPoint = convertFromUnitToNormal(v, viewport);
return unproject(projectedPoint, viewport);
}
Vector3 GCamera::unproject(const Vector3& v, const Rect2D& viewport) const
{
const float n = m_nearPlaneZ;
const float f = m_farPlaneZ;
float z;
if (-f >= finf())
{
// Infinite far plane
z = 1.0f / (((-1.0f / n) * v.z) + 1.0f / n);
}
else
{
z = 1.0f / ((((1.0f / f) - (1.0f / n)) * v.z) + 1.0f / n);
}
const Ray& ray = worldRay(v.x - m_pixelOffset.x, v.y - m_pixelOffset.y, viewport);
// Find out where the ray reaches the specified depth.
const Vector3& out = ray.origin() + ray.direction() * -z / (ray.direction().dot(m_cframe.lookVector()));
return out;
}
float GCamera::worldToScreenSpaceArea(float area, float z, const Rect2D& viewport) const
{
(void)viewport;
if (z >= 0)
{
return finf();
}
return area * (float)square(imagePlaneDepth() / z);
}
void GCamera::getClipPlanes(const Rect2D& viewport, Array<Plane>& clip) const
{
Frustum fr;
frustum(viewport, fr);
clip.resize(fr.faceArray.size(), DONT_SHRINK_UNDERLYING_ARRAY);
for (int f = 0; f < clip.size(); ++f)
{
clip[f] = fr.faceArray[f].plane;
}
}
GCamera::Frustum GCamera::frustum(const Rect2D& viewport) const
{
Frustum f;
frustum(viewport, f);
return f;
}
void GCamera::frustum(const Rect2D& viewport, Frustum& fr) const
{
// The volume is the convex hull of the vertices definining the view
// frustum and the light source point at infinity.
const float x = viewportWidth(viewport) / 2;
const float y = viewportHeight(viewport) / 2;
const float zn = m_nearPlaneZ;
const float zf = m_farPlaneZ;
float xx, zz, yy;
float halfFOV = m_fieldOfView * 0.5f;
// This computes the normal, which is based on the complement of the
// halfFOV angle, so the equations are "backwards"
if (m_direction == VERTICAL)
{
yy = -cosf(halfFOV);
xx = yy * viewport.height() / viewport.width();
zz = -sinf(halfFOV);
}
else
{
xx = -cosf(halfFOV);
yy = xx * viewport.width() / viewport.height();
zz = -sinf(halfFOV);
}
// Near face (ccw from UR)
fr.vertexPos.append(Vector4(x, y, zn, 1), Vector4(-x, y, zn, 1), Vector4(-x, -y, zn, 1), Vector4(x, -y, zn, 1));
// Far face (ccw from UR, from origin)
if (m_farPlaneZ == -finf())
{
fr.vertexPos.append(Vector4(x, y, zn, 0), Vector4(-x, y, zn, 0), Vector4(-x, -y, zn, 0), Vector4(x, -y, zn, 0));
}
else
{
// Finite
const float s = zf / zn;
fr.vertexPos.append(
Vector4(x * s, y * s, zf, 1), Vector4(-x * s, y * s, zf, 1), Vector4(-x * s, -y * s, zf, 1), Vector4(x * s, -y * s, zf, 1));
}
Frustum::Face face;
// Near plane (wind backwards so normal faces into frustum)
// Recall that nearPlane, farPlane are positive numbers, so
// we need to negate them to produce actual z values.
face.plane = Plane(Vector3(0, 0, -1), Vector3(0, 0, m_nearPlaneZ));
face.vertexIndex[0] = 3;
face.vertexIndex[1] = 2;
face.vertexIndex[2] = 1;
face.vertexIndex[3] = 0;
fr.faceArray.append(face);
// Right plane
face.plane = Plane(Vector3(xx, 0, zz), Vector3::zero());
face.vertexIndex[0] = 0;
face.vertexIndex[1] = 4;
face.vertexIndex[2] = 7;
face.vertexIndex[3] = 3;
fr.faceArray.append(face);
// Left plane
face.plane = Plane(Vector3(-fr.faceArray.last().plane.normal().x, 0, fr.faceArray.last().plane.normal().z), Vector3::zero());
face.vertexIndex[0] = 5;
face.vertexIndex[1] = 1;
face.vertexIndex[2] = 2;
face.vertexIndex[3] = 6;
fr.faceArray.append(face);
// Top plane
face.plane = Plane(Vector3(0, yy, zz), Vector3::zero());
face.vertexIndex[0] = 1;
face.vertexIndex[1] = 5;
face.vertexIndex[2] = 4;
face.vertexIndex[3] = 0;
fr.faceArray.append(face);
// Bottom plane
face.plane = Plane(Vector3(0, -fr.faceArray.last().plane.normal().y, fr.faceArray.last().plane.normal().z), Vector3::zero());
face.vertexIndex[0] = 2;
face.vertexIndex[1] = 3;
face.vertexIndex[2] = 7;
face.vertexIndex[3] = 6;
fr.faceArray.append(face);
// Far plane
if (-m_farPlaneZ < finf())
{
face.plane = Plane(Vector3(0, 0, 1), Vector3(0, 0, m_farPlaneZ));
face.vertexIndex[0] = 4;
face.vertexIndex[1] = 5;
face.vertexIndex[2] = 6;
face.vertexIndex[3] = 7;
fr.faceArray.append(face);
}
// Transform vertices to world space
for (int v = 0; v < fr.vertexPos.size(); ++v)
{
fr.vertexPos[v] = m_cframe.toWorldSpace(fr.vertexPos[v]);
}
// Transform planes to world space
for (int p = 0; p < fr.faceArray.size(); ++p)
{
// Since there is no scale factor, we don't have to
// worry about the inverse transpose of the normal.
Vector3 normal;
float d;
fr.faceArray[p].plane.getEquation(normal, d);
Vector3 newNormal = m_cframe.rotation * normal;
if (isFinite(d))
{
d = (newNormal * -d + m_cframe.translation).dot(newNormal);
fr.faceArray[p].plane = Plane(newNormal, newNormal * d);
}
else
{
// When d is infinite, we can't multiply 0's by it without
// generating NaNs.
fr.faceArray[p].plane = Plane::fromEquation(newNormal.x, newNormal.y, newNormal.z, d);
}
}
}
void GCamera::getNearViewportCorners(const Rect2D& viewport, Vector3& outUR, Vector3& outUL, Vector3& outLL, Vector3& outLR) const
{
// Must be kept in sync with getFrustum()
const float w = viewportWidth(viewport) / 2.0f;
const float h = viewportHeight(viewport) / 2.0f;
const float z = nearPlaneZ();
// Compute the points
outUR = Vector3(w, h, z);
outUL = Vector3(-w, h, z);
outLL = Vector3(-w, -h, z);
outLR = Vector3(w, -h, z);
// Take to world space
outUR = m_cframe.pointToWorldSpace(outUR);
outUL = m_cframe.pointToWorldSpace(outUL);
outLR = m_cframe.pointToWorldSpace(outLR);
outLL = m_cframe.pointToWorldSpace(outLL);
}
void GCamera::getFarViewportCorners(const Rect2D& viewport, Vector3& outUR, Vector3& outUL, Vector3& outLL, Vector3& outLR) const
{
// Must be kept in sync with getFrustum()
const float w = viewportWidth(viewport) * m_farPlaneZ / m_nearPlaneZ;
const float h = viewportHeight(viewport) * m_farPlaneZ / m_nearPlaneZ;
const float z = m_farPlaneZ;
// Compute the points
outUR = Vector3(w / 2, h / 2, z);
outUL = Vector3(-w / 2, h / 2, z);
outLL = Vector3(-w / 2, -h / 2, z);
outLR = Vector3(w / 2, -h / 2, z);
// Take to world space
outUR = m_cframe.pointToWorldSpace(outUR);
outUL = m_cframe.pointToWorldSpace(outUL);
outLR = m_cframe.pointToWorldSpace(outLR);
outLL = m_cframe.pointToWorldSpace(outLL);
}
void GCamera::setPosition(const Vector3& t)
{
m_cframe.translation = t;
}
void GCamera::lookAt(const Vector3& position, const Vector3& up)
{
m_cframe.lookAt(position, up);
}
Vector3 GCamera::convertFromUnitToNormal(const Vector3& in, const Rect2D& viewport) const
{
return (in + Vector3(1, 1, 1)) * 0.5 * Vector3(viewport.width(), -viewport.height(), 1) + Vector3(viewport.x0(), viewport.y1(), 0);
}
} // namespace G3D

334
engine/3d/src/GCamera.hpp Normal file
View File

@@ -0,0 +1,334 @@
/**
Aya - We are not using the G3D::GCamera, intstead we are using the Aya:Rbx::Camera from old G3D.
@file GCamera.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2005-07-20
@edited 2009-04-20
*/
#ifndef G3D_GCamera_H
#define G3D_GCamera_H
#include "platform.hpp"
#include "CoordinateFrame.hpp"
#include "Vector3.hpp"
#include "Plane.hpp"
#include "debugAssert.hpp"
namespace G3D
{
class Matrix4;
class Rect2D;
/**
Abstraction of a pinhole camera.
The area a camera sees is called a frustum. It is bounded by the
near plane, the far plane, and the sides of the view frame projected
into the scene. It has the shape of a pyramid with the top cut off.
Cameras can project points from 3D to 2D. The "unit" projection
matches OpenGL. It maps the entire view frustum to a cube of unit
radius (i.e., edges of length 2) centered at the origin. The
non-unit projection then maps that cube to the specified pixel
viewport in X and Y and the range [0, 1] in Z. The projection is
reversable as long as the projected Z value is known.
All viewport arguments are the pixel bounds of the viewport-- e.g.,
RenderDevice::viewport().
See http://bittermanandy.wordpress.com/2009/04/10/a-view-to-a-thrill-part-one-camera-concepts/
for a nice introduction to camera transformations.
*/
class GCamera
{
public:
/**
Stores the direction of the field of view
*/
enum FOVDirection
{
HORIZONTAL,
VERTICAL
};
private:
/** Full field of view (in radians) */
float m_fieldOfView;
/** Clipping plane, *not* imaging plane. Negative numbers. */
float m_nearPlaneZ;
/** Negative */
float m_farPlaneZ;
/** Stores the camera's location and orientation */
CoordinateFrame m_cframe;
/** Horizontal or Vertical */
FOVDirection m_direction;
Vector2 m_pixelOffset;
public:
class Frustum
{
public:
class Face
{
public:
/** Counter clockwise indices into vertexPos */
int vertexIndex[4];
/** The plane containing the face. */
Plane plane;
};
/** The vertices, in homogeneous space. If w == 0,
a vertex is at infinity. */
Array<Vector4> vertexPos;
/** The faces in the frustum. When the
far plane is at infinity, there are 5 faces,
otherwise there are 6. The faces are in the order
N,R,L,B,T,[F].
*/
Array<Face> faceArray;
};
GCamera();
GCamera(const Matrix4& proj, const CFrame& frame);
virtual ~GCamera();
/** Returns the current coordinate frame */
const CoordinateFrame& coordinateFrame() const
{
return m_cframe;
}
/** Displacement from the upper left added in pixels in screen
space to the projection matrix. This is useful for shifting
the sampled location from the pixel center (OpenGL convention)
to other locations, such as the upper-left.*/
void setPixelOffset(const Vector2& p)
{
m_pixelOffset = p;
}
const Vector2& pixelOffset() const
{
return m_pixelOffset;
}
/** Sets c to the camera's coordinate frame */
void getCoordinateFrame(CoordinateFrame& c) const;
/** Sets a new coordinate frame for the camera */
void setCoordinateFrame(const CoordinateFrame& c);
/** Sets \a P equal to the camera's projection matrix. This is the
matrix that maps points to the homogeneous clip cube that
varies from -1 to 1 on all axes. The projection matrix does
not include the camera transform.
This is the matrix that a RenderDevice (or OpenGL) uses as the projection matrix.
@sa RenderDevice::setProjectionAndCameraMatrix, RenderDevice::setProjectionMatrix, Matrix4::perspectiveProjection
*/
void getProjectUnitMatrix(const Rect2D& viewport, Matrix4& P) const;
/** Sets \a P equal to the matrix that transforms points to pixel
coordinates on the given viewport. A point correspoinding to
the top-left corner of the viewport in camera space will
transform to viewport.x0y0() and the bottom-right to viewport.x1y1(). */
void getProjectPixelMatrix(const Rect2D& viewport, Matrix4& P) const;
/** Converts projected points from OpenGL standards
(-1, 1) to normal 3D coordinate standards (0, 1)
\deprecated
*/ // TODO: Remove
Vector3 convertFromUnitToNormal(const Vector3& in, const Rect2D& viewport) const;
/**
Sets the field of view, in radians. The
initial angle is toRadians(55). Must specify
the direction of the angle.
This is the full angle, i.e., from the left side of the
viewport to the right side.
*/
void setFieldOfView(float edgeToEdgeAngleRadians, FOVDirection direction);
/** Returns the current full field of view angle (from the left side of the
viewport to the right side) and direction */
inline void getFieldOfView(float& angle, FOVDirection& direction) const
{
angle = m_fieldOfView;
direction = m_direction;
}
/**
Projects a world space point onto a width x height screen. The
returned coordinate uses pixmap addressing: x = right and y =
down. The resulting z value is 0 at the near plane, 1 at the far plane,
and is a linear compression of unit cube projection.
If the point is behind the camera, Vector3::inf() is returned.
*/
Vector3 project(const G3D::Vector3& point, const class Rect2D& viewport) const;
/**
Projects a world space point onto a unit cube. The resulting
x,y,z values range between -1 and 1, where z is -1
at the near plane and 1 at the far plane and varies hyperbolically in between.
If the point is behind the camera, Vector3::inf() is returned.
*/
Vector3 projectUnit(const G3D::Vector3& point, const class Rect2D& viewport) const;
/**
Gives the world-space coordinates of screen space point v, where
v.x is in pixels from the left, v.y is in pixels from
the top, and v.z is on the range 0 (near plane) to 1 (far plane).
*/
Vector3 unproject(const Vector3& v, const Rect2D& viewport) const;
/**
Gives the world-space coordinates of unit cube point v, where
v varies from -1 to 1 on all axes. The unproject first
transforms the point into a pixel location for the viewport, then calls unproject
*/
Vector3 unprojectUnit(const Vector3& v, const Rect2D& viewport) const;
/**
Returns the pixel area covered by a shape of the given
world space area at the given z value (z must be negative).
*/
float worldToScreenSpaceArea(float area, float z, const class Rect2D& viewport) const;
/**
Returns the world space 3D viewport corners. These
are at the near clipping plane. The corners are constructed
from the nearPlaneZ, viewportWidth, and viewportHeight.
"left" and "right" are from the GCamera's perspective.
*/
void getNearViewportCorners(const class Rect2D& viewport, Vector3& outUR, Vector3& outUL, Vector3& outLL, Vector3& outLR) const;
/**
Returns the world space 3D viewport corners. These
are at the Far clipping plane. The corners are constructed
from the nearPlaneZ, farPlaneZ, viewportWidth, and viewportHeight.
"left" and "right" are from the GCamera's perspective.
*/
void getFarViewportCorners(const class Rect2D& viewport, Vector3& outUR, Vector3& outUL, Vector3& outLL, Vector3& outLR) const;
/**
Returns the image plane depth, assumes imagePlane
is the same as the near clipping plane.
returns a positive number.
*/
float imagePlaneDepth() const;
/**
Returns the world space ray passing through the center of pixel
(x, y) on the image plane. The pixel x and y axes are opposite
the 3D object space axes: (0,0) is the upper left corner of the screen.
They are in viewport coordinates, not screen coordinates.
The ray origin is at the origin. To start it at the image plane,
move it forward by imagePlaneDepth/ray.direction.z
Integer (x, y) values correspond to
the upper left corners of pixels. If you want to cast rays
through pixel centers, add 0.5 to x and y.
*/
Ray worldRay(float x, float y, const class Rect2D& viewport) const;
/**
Returns a negative z-value.
*/
inline float nearPlaneZ() const
{
return m_nearPlaneZ;
}
/**
Returns a negative z-value.
*/
inline float farPlaneZ() const
{
return m_farPlaneZ;
}
/**
Sets a new value for the far clipping plane
Expects a negative value
*/
inline void setFarPlaneZ(float z)
{
debugAssert(z < 0);
m_farPlaneZ = z;
}
/**
Sets a new value for the near clipping plane
Expects a negative value
*/
inline void setNearPlaneZ(float z)
{
debugAssert(z < 0);
m_nearPlaneZ = z;
}
/**
Returns the camera space width of the viewport at the near plane.
*/
float viewportWidth(const class Rect2D& viewport) const;
/**
Returns the camera space height of the viewport at the near plane.
*/
float viewportHeight(const class Rect2D& viewport) const;
void setPosition(const Vector3& t);
/** Rotate the camera in place to look at the target. Does not
persistently look at that location when the camera moves;
i.e., if you move the camera and still want it to look at the
old target, you must call lookAt again after moving the
camera.)*/
void lookAt(const Vector3& position, const Vector3& up = Vector3::unitY());
/**
Returns the clipping planes of the frustum, in world space.
The planes have normals facing <B>into</B> the view frustum.
The plane order is guaranteed to be:
Near, Right, Left, Top, Bottom, [Far]
If the far plane is at infinity, the resulting array will have
5 planes, otherwise there will be 6.
The viewport is used only to determine the aspect ratio of the screen; the
absolute dimensions and xy values don't matter.
*/
void getClipPlanes(const Rect2D& viewport, Array<Plane>& outClip) const;
/**
Returns the world space view frustum, which is a truncated pyramid describing
the volume of space seen by this camera.
*/
void frustum(const Rect2D& viewport, GCamera::Frustum& f) const;
GCamera::Frustum frustum(const Rect2D& viewport) const;
};
} // namespace G3D
#endif

1414
engine/3d/src/GImage.cpp Normal file

File diff suppressed because it is too large Load Diff

538
engine/3d/src/GImage.hpp Normal file
View File

@@ -0,0 +1,538 @@
/**
\file GImage.h
See G3D::GImage for details.
@cite JPEG compress/decompressor is the <A HREF="http://www.ijg.org/files/">IJG library</A>, used in accordance with their license.
@cite JPG code by John Chisholm, using the IJG Library
@cite TGA code by Morgan McGuire
@cite BMP code by John Chisholm, based on code by Edward "CGameProgrammer" Resnick <A HREF="mailto:cgp@gdnmail.net">mailto:cgp@gdnmail.net</A> at <A
HREF="ftp://ftp.flipcode.com/cotd/LoadPicture.txt">ftp://ftp.flipcode.com/cotd/LoadPicture.txt</A>
@cite PCX format described in the ZSOFT PCX manual http://www.nist.fss.ru/hr/doc/spec/pcx.htm#2
@cite PNG compress/decompressor is the <A HREF="http://www.libpng.org/pub/png/libpng.html">libpng library</A>, used in accordance with their
license.
@cite PPM code by Morgan McGuire based on http://netpbm.sourceforge.net/doc/ppm.html
\maintainer Morgan McGuire, http://graphics.cs.williams.edu
\created 2002-05-27
\edited 2010-01-04
Copyright 2000-2010, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_GImage_h
#define G3D_GImage_h
#include "platform.hpp"
#include <string>
#include "Array.hpp"
#include "g3dmath.hpp"
#include "stringutils.hpp"
#include "Color1uint8.hpp"
#include "Color3uint8.hpp"
#include "Color4uint8.hpp"
#include "MemoryManager.hpp"
#include "BumpMapPreprocess.hpp"
namespace G3D
{
class BinaryInput;
class BinaryOutput;
/**
Interface to image compression & file formats.
Supported formats (decode and encode): Color JPEG, PNG,
(Uncompressed) TGA 24, (Uncompressed) TGA 32, BMP 1, BMP 4, BMP 8, BMP
24, PPM (P6), and PPM ASCII (P1, P2, P3), which includes PPM, PGM, PBM,
and JXL. (Compressed) TGA 24, (Compressed) TGA 32, 8-bit paletted PCX, 24-bit PCX, and ICO are supported for
decoding only.
Sample usage:
\verbatim
// Loading from disk:
G3D::GImage im1("test.jpg");
// Loading from memory:
G3D::GImage im2(data, length);
// im.pixel is a pointer to RGB color data. If you want
// an alpha channel, call RGBtoRGBA or RGBtoARGB for
// conversion.
// Saving to memory:
G3D::GImage im3(width, height);
// (Set the pixels of im3...)
uint8* data2;
int len2;
im3.encode(G3D::GImage::JPEG, data2, len2);
// Saving to disk
im3.save("out.jpg");
\endverbatim
The free Image Magick Magick Wand API
(http://www.imagemagick.org/www/api/magick_wand.html) provides a more powerful
API for image manipulation and wider set of image load/save formats. It is
recommended over GImage (we don't include it directly in G3D because their license
is more restrictive than the BSD one).
\cite http://tfcduke.developpez.com/tutoriel/format/tga/fichiers/tga_specs.pdf
\sa Image3, Image3uint8, Image4, Image4uint8, Image1, Image1uint8, Texture, Map2D
*/
class GImage
{
private:
/** Used exclusively for allocating m_byte; this may be an
implementation that allocates directly on a GPU.*/
MemoryManager::Ref m_memMan;
uint8* m_byte;
int m_channels;
int m_width;
int m_height;
public:
class Error
{
public:
Error(const std::string& reason, const std::string& filename = "")
: reason(reason)
, filename(filename)
{
}
std::string reason;
std::string filename;
};
enum Format
{
JPEG,
JXL,
BMP,
TGA,
PCX,
ICO,
PNG,
AUTODETECT,
UNKNOWN,
DDS,
PVR
};
/**
The number of channels; either 3 (RGB) or 4 (RGBA)
*/
inline int channels() const
{
return m_channels;
}
inline int width() const
{
return m_width;
}
inline int height() const
{
return m_height;
}
inline const uint8* byte() const
{
return m_byte;
}
/** Returns a pointer to the underlying data, which is stored
in row-major order without row padding.
e.g., <code>uint8* ptr = image.rawData<uint8>();
*/
template<typename Type>
inline const Type* rawData() const
{
return (Type*)m_byte;
}
/** \copybrief GImage::rawData() const */
template<typename Type>
inline Type* rawData()
{
return (Type*)m_byte;
}
inline const Color1uint8* pixel1() const
{
debugAssertM(m_channels == 1, format("Tried to call GImage::pixel1 on an image with %d channels", m_channels));
return (Color1uint8*)m_byte;
}
inline Color1uint8* pixel1()
{
debugAssertM(m_channels == 1, format("Tried to call GImage::pixel1 on an image with %d channels", m_channels));
return (Color1uint8*)m_byte;
}
/** Returns a pointer to the upper left pixel
as Color4uint8.
*/
inline const Color4uint8* pixel4() const
{
debugAssertM(m_channels == 4, format("Tried to call GImage::pixel4 on an image with %d channels", m_channels));
return (Color4uint8*)m_byte;
}
inline Color4uint8* pixel4()
{
debugAssert(m_channels == 4);
return (Color4uint8*)m_byte;
}
/** Returns a pointer to the upper left pixel
as Color3uint8.
*/
inline const Color3uint8* pixel3() const
{
debugAssertM(m_channels == 3, format("Tried to call GImage::pixel3 on an image with %d channels", m_channels));
return (Color3uint8*)m_byte;
}
inline Color3uint8* pixel3()
{
debugAssert(m_channels == 3);
return (Color3uint8*)m_byte;
}
/** Returns the pixel at (x, y), where (0,0) is the upper left. */
inline const Color1uint8& pixel1(int x, int y) const
{
debugAssert(x >= 0 && x < m_width);
debugAssert(y >= 0 && y < m_height);
return pixel1()[x + y * m_width];
}
/** Returns the pixel at (x, y), where (0,0) is the upper left. */
inline Color1uint8& pixel1(int x, int y)
{
debugAssert(x >= 0 && x < m_width);
debugAssert(y >= 0 && y < m_height);
return pixel1()[x + y * m_width];
}
/** Returns the pixel at (x, y), where (0,0) is the upper left. */
inline const Color3uint8& pixel3(int x, int y) const
{
debugAssert(x >= 0 && x < m_width);
debugAssert(y >= 0 && y < m_height);
return pixel3()[x + y * m_width];
}
inline Color3uint8& pixel3(int x, int y)
{
debugAssert(x >= 0 && x < m_width);
debugAssert(y >= 0 && y < m_height);
return pixel3()[x + y * m_width];
}
/** Returns the pixel at (x, y), where (0,0) is the upper left. */
inline const Color4uint8& pixel4(int x, int y) const
{
debugAssert(x >= 0 && x < m_width);
debugAssert(y >= 0 && y < m_height);
return pixel4()[x + y * m_width];
}
inline Color4uint8& pixel4(int x, int y)
{
debugAssert(x >= 0 && x < m_width);
debugAssert(y >= 0 && y < m_height);
return pixel4()[x + y * m_width];
}
inline uint8* byte()
{
return m_byte;
}
// Aya
G3D::Color4uint8 getPixel(int x, int y) const;
void setPixel(int x, int y, G3D::Color4uint8 val);
/**
Resizes the image using bilinear interpolation.
All software and _slow_!
*/
GImage bilinearStretchBlt(int newwidth, int newheight) const;
/**
Resizes the image.
All software and _slow_!
*/
GImage stretchBlt(int newwidth, int newheight) const;
// ======
private:
void encodeBMP(BinaryOutput& out) const;
/**
The TGA file will be either 24- or 32-bit depending
on the number of channels.
*/
void encodeTGA(BinaryOutput& out) const;
/**
Converts this image into a JPEG
*/
void encodeJPEG(BinaryOutput& out) const;
/**
Converts this image into a JPEG
*/
void encodePNG(BinaryOutput& out) const;
void encodeJXL(BinaryOutput& out) const;
void decodeJXL(BinaryInput& input);
void decodeTGA(BinaryInput& input);
void decodeBMP(BinaryInput& input);
void decodeJPEG(BinaryInput& input);
void decodePCX(BinaryInput& input);
void decodeICO(BinaryInput& input);
void decodePNG(BinaryInput& input);
void _copy(const GImage& other);
public:
// Aya: moved from private to public
/**
Given [maybe] a filename, memory buffer, and [maybe] a format,
returns the most likely format of this file.
*/
static Format resolveFormat(const std::string& filename, const uint8* data, int dataLen, Format maybeFormat);
// ================
void flipHorizontal();
void flipVertical();
void rotate90CW(int numTimes = 1);
/**
Create an empty image of the given size.
\sa load()
*/
GImage(int width = 0, int height = 0, int channels = 3, const MemoryManager::Ref& m = MemoryManager::create());
/**
Decodes an image stored in a buffer.
*/
GImage(const unsigned char* data, int length, Format format = AUTODETECT, const MemoryManager::Ref& m = MemoryManager::create());
GImage(const GImage& other, const MemoryManager::Ref& m = MemoryManager::create());
GImage& operator=(const GImage& other);
/**
Returns a new GImage that has 4 channels. RGB is
taken from this GImage and the alpha from the red
channel of the supplied image. The new GImage is passed
as a reference parameter for speed.
*/
void insertRedAsAlpha(const GImage& alpha, GImage& output) const;
/**
Returns a new GImage with 3 channels, removing
the alpha channel if there is one. The new GImage
is passed as a reference parameter for speed.
*/
void stripAlpha(GImage& output) const;
// Aya
GImage stripAlpha() const;
void setColorAlphaTest(const Color4uint8& color);
// ======
/**
Frees memory and resets to a 0x0 image.
*/
void clear();
/**
Deallocates the pixels.
*/
virtual ~GImage();
/**
Resizes the internal buffer to (\a width x \a height) with the
number of \a channels specified.
\param zero If true, all data is set to 0 (black).
*/
void resize(int width, int height, int channels, bool zero = true);
/**
Copies src sub-image data into dest at a certain offset.
The dest variable must already contain an image that is large
enough to contain the src sub-image at the specified offset.
Returns true on success and false if the src sub-image cannot
completely fit within dest at the specified offset. Both
src and dest must have the same number of channels.
*/
static bool pasteSubImage(GImage& dest, const GImage& src, int destX, int destY, int srcX, int srcY, int srcWidth, int srcHeight);
// Aya
static bool rotateAndPasteSubImage(
GImage& dest, GImage& src, int destX, int destY, int srcX, int srcY, int srcWidth, int srcHeight, int rotation);
static G3D::Color4uint8 getPixel(const GImage& img, int x, int y);
static void setPixel(GImage& img, int x, int y, G3D::Color4uint8 val);
static G3D::Color4uint8 interpolateColor(G3D::Color4uint8 c1, G3D::Color4uint8 c2, float p);
static void cornerGrad(GImage& src, int x, int y, int w, int h, int rotation);
// =====
/**
creates dest from src sub-image data.
Returns true on success and false if the src sub-image
is not within src.
*/
static bool copySubImage(GImage& dest, const GImage& src, int srcX, int srcY, int srcWidth, int srcHeight);
void convertToRGBA();
void convertToRGB();
/** Averages color channels if they exist */
void convertToL8();
/**
Returns true if format is supported. Format
should be an extension string (e.g. "BMP").
*/
static bool supportedFormat(const std::string& format);
/**
Converts a string to an enum, returns UNKNOWN if not recognized.
*/
static Format stringToFormat(const std::string& format);
/**
The caller must delete the returned buffer.
TODO: provide a memory manager
*/
void encode(Format format, uint8*& outData, int& outLength) const;
/**
Does not commit the BinaryOutput when done.
*/
void encode(Format format, BinaryOutput& out) const;
/**
Decodes the buffer into this image.
@param format Must be the correct format.
*/
void decode(BinaryInput& input, Format format);
/** Returns the size of this object in bytes */
int sizeInMemory() const;
/** Ok for in == out */
static void R8G8B8_to_Y8U8V8(int width, int height, const uint8* in, uint8* out);
/** Ok for in == out */
static void Y8U8V8_to_R8G8B8(int width, int height, const uint8* in, uint8* out);
/**
@param in RGB buffer of numPixels * 3 bytes
@param out Buffer of numPixels * 4 bytes
@param numPixels Number of RGB pixels to convert
*/
static void RGBtoRGBA(const uint8* in, uint8* out, int numPixels);
static void RGBtoARGB(const uint8* in, uint8* out, int numPixels);
static void LtoRGB(const uint8* in, uint8* out, int numPixels);
static void LtoRGBA(const uint8* in, uint8* out, int numPixels);
/** Safe for in == out */
static void RGBtoBGR(const uint8* in, uint8* out, int numPixels);
/**
Win32 32-bit HDC format.
*/
static void RGBtoBGRA(const uint8* in, uint8* out, int numPixels);
static void RGBAtoRGB(const uint8* in, uint8* out, int numPixels);
/**
Uses the red channel of the second image as an alpha channel.
*/
static void RGBxRGBtoRGBA(const uint8* colorRGB, const uint8* alphaRGB, uint8* out, int numPixels);
/**
Flips the image along the vertical axis.
Safe for in == out.
*/
static void flipRGBVertical(const uint8* in, uint8* out, int width, int height);
static void flipRGBAVertical(const uint8* in, uint8* out, int width, int height);
/**
Given a tangent space bump map, computes a new image where the
RGB channels are a tangent space normal map and the alpha channel
is the original bump map. Assumes the input image is tileable.
In the resulting image, x = red = tangent, y = green = binormal, and z = blue = normal.
Particularly useful as part of the idiom:
<PRE>
GImage normal;
computeNormalMap(GImage(filename), normal);
return Texture::fromGImage(filename, normal);
</PRE>
*/
static void computeNormalMap(const class GImage& bump, class GImage& normal, const BumpMapPreprocess& preprocess = BumpMapPreprocess());
static void computeNormalMap(
int width, int height, int channels, const uint8* src, GImage& normal, const BumpMapPreprocess& preprocess = BumpMapPreprocess());
/**
Bayer demosaicing using the filter proposed in
HIGH-QUALITY LINEAR INTERPOLATION FOR DEMOSAICING OF BAYER-PATTERNED COLOR IMAGES
Henrique S. Malvar, Li-wei He, and Ross Cutler
The filter wraps at the image boundaries.
Assumes in != out.
*/
static void BAYER_G8B8_R8G8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out);
static void BAYER_G8R8_B8G8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out);
static void BAYER_R8G8_G8B8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out);
static void BAYER_B8G8_G8R8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out);
/** Fast conversion; the output has 1/2 the size of the input in each direction. Assumes in != out.
See G3D::BAYER_G8B8_R8G8_to_R8G8B8_MHC for a much better result. */
static void BAYER_G8B8_R8G8_to_Quarter_R8G8B8(int inWidth, int inHeight, const uint8* in, uint8* out);
/** Attempt to undo fast conversion of G3D::BAYER_G8B8_R8G8_to_Quarter_R8G8B8;
the green channel will lose data. Assumes in != out
The input should have size 3 * inWidth * inHeight. The output should have size
2 * inWidth * 2 * inHeight.
*/
static void Quarter_R8G8B8_to_BAYER_G8B8_R8G8(int inWidth, int inHeight, const uint8* in, uint8* out);
/** Overwrites every pixel with one of the two colors in a checkerboard pattern.
The fields used from the two colors depend on the current number of channels in @a im.
*/
static void makeCheckerboard(GImage& im, int checkerSize = 1, const Color4uint8& color1 = Color4uint8(255, 255, 255, 255),
const Color4uint8& color2 = Color4uint8(0, 0, 0, 255));
};
} // namespace G3D
#endif

View File

@@ -0,0 +1,306 @@
/**
@file GImage_bayer.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-05-27
@edited 2006-05-10
*/
#include "platform.hpp"
#include "GImage.hpp"
namespace G3D
{
void GImage::BAYER_G8B8_R8G8_to_Quarter_R8G8B8(int width, int height, const uint8* in, uint8* out)
{
debugAssert(in != out);
int halfHeight = height / 2;
int halfWidth = width / 2;
int dst_off = 0;
for (int y = 0; y < halfHeight; ++y)
{
for (int x = 0; x < halfWidth; ++x)
{
// GBRG
int src_off = x * 2 + y * 2 * width;
out[dst_off] = in[src_off + width]; // red
out[dst_off + 1] = ((int)in[src_off] + (int)in[src_off + width + 1]) / 2; // green
out[dst_off + 2] = in[src_off + 1]; // blue
dst_off = dst_off + 3;
}
}
}
void GImage::Quarter_R8G8B8_to_BAYER_G8B8_R8G8(int inWidth, int inHeight, const uint8* in, uint8* out)
{
// Undo quarter-size Bayer as best we can. This code isn't very efficient, but it
// also isn't used very frequently.
debugAssert(out != in);
int outWidth = 2 * inWidth;
int outHeight = 2 * inHeight;
for (int y = 0; y < outHeight; ++y)
{
for (int x = 0; x < outWidth; ++x)
{
const Color3uint8* inp = ((const Color3uint8*)in) + ((x / 2) + (y / 2) * inWidth);
uint8* outp = out + x + y * outWidth;
if (isEven(y))
{
// GB row
if (isEven(x))
{
// Green
*outp = inp->g;
}
else
{
// Blue
*outp = inp->b;
}
}
else
{
// RG row
if (isEven(x))
{
// Red
*outp = inp->r;
}
else
{
// Green
*outp = inp->g;
}
}
}
}
}
/** Applies a 5x5 filter to monochrome image I (wrapping at the boundaries) */
static uint8 applyFilter(const uint8* I, int x, int y, int w, int h, const float filter[5][5])
{
debugAssert(isEven(w));
debugAssert(isEven(h));
float sum = 0.0f;
float denom = 0.0f;
for (int dy = 0; dy < 5; ++dy)
{
int offset = ((y + dy + h - 2) % h) * w;
for (int dx = 0; dx < 5; ++dx)
{
float f = filter[dy][dx];
sum += f * I[((x + dx + w - 2) % w) + offset];
denom += f;
}
}
return (uint8)iClamp(iRound(sum / denom), 0, 255);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//
// Bayer conversions
//
// There are two kinds of rows (GR and BG).
// In each row, there are two kinds of pixels (G/R, B/G).
// We express the four kinds of INPUT pixels as:
// GRG, GRG, BGB, BGG
//
// There are three kinds of OUTPUT pixels: R, G, B.
// Thus there are nominally 12 different I/O combinations,
// but several are impulses because needed output at that
// location *is* the input (e.g., G_GRG and G_BGG).
//
// The following 5x5 row-major filters are named as output_input.
// Green
static const float G_GRR[5][5] = {{0.0f, 0.0f, -1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 2.0f, 0.0f, 0.0f}, {-1.0f, 2.0f, 4.0f, 2.0f, -1.0f},
{0.0f, 0.0f, 2.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f, 0.0f, 0.0f}};
static const float G_BGB[5][5] = {{0.0f, 0.0f, -1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 2.0f, 0.0f, 0.0f}, {-1.0f, 2.0f, 4.0f, 2.0f, -1.0f},
{0.0f, 0.0f, 2.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f, 0.0f, 0.0f}};
// Red
//(the caption in the paper is wrong for this case:
// "R row B column really means R row G column"
static const float R_GRG[5][5] = {{0.0f, 0.0f, 0.5f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f, -1.0f, 0.0f}, {-1.0f, 4.0f, 5.0f, 4.0f, -1.0f},
{0.0f, -1.0f, 0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 0.5f, 0.0f, 0.0f}};
static const float R_BGG[5][5] = {{0.0f, 0.0f, -1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 4.0f, -1.0f, 0.0f}, {0.5f, 0.0f, 5.0f, 0.0f, 0.5f},
{0.0f, -1.0f, 4.0f, -1.0f, 0.0f}, {0.0f, 0.0f, -1.0f, 0.0f, 0.0f}};
static const float R_BGB[5][5] = {{0.0f, 0.0f, -3.0f / 2.0f, 0.0f, 0.0f}, {0.0f, 2.0f, 0.0f, 2.0f, 0.0f},
{-3.0f / 2.0f, 0.0f, 6.0f, 0.0f, -3.0f / 2.0f}, {0.0f, 2.0f, 0.0f, 2.0f, 0.0f}, {0.0f, 0.0f, -3.0f / 2.0f, 0.0f, 0.0f}};
// Blue
//(the caption in the paper is wrong for this case:
// "B row R column really means B row G column")
#define B_BGG R_GRG
#define B_GRG R_BGG
#define B_GRR R_BGB
void GImage::BAYER_R8G8_G8B8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out)
{
debugAssert(in != _out);
Color3uint8* out = (Color3uint8*)_out;
for (int y = 0; y < h; ++y)
{
// Row beginning in the input array.
int offset = y * w;
// RG row
for (int x = 0; x < w; ++x, ++out)
{
// R pixel
{
out->r = in[x + offset];
out->g = applyFilter(in, x, y, w, h, G_GRR);
out->b = applyFilter(in, x, y, w, h, B_GRR);
}
++x;
++out;
// G pixel
{
out->r = applyFilter(in, x, y, w, h, R_GRG);
out->g = in[x + offset];
out->b = applyFilter(in, x, y, w, h, B_GRG);
}
}
++y;
offset += w;
// GB row
for (int x = 0; x < w; ++x, ++out)
{
// G pixel
{
out->r = applyFilter(in, x, y, w, h, R_BGG);
out->g = in[x + offset];
out->b = applyFilter(in, x, y, w, h, B_BGG);
}
++x;
++out;
// B pixel
{
out->r = applyFilter(in, x, y, w, h, R_BGB);
out->g = applyFilter(in, x, y, w, h, G_BGB);
out->b = in[x + offset];
}
}
}
}
static void swapRedAndBlue(int N, Color3uint8* out)
{
for (int i = N - 1; i >= 0; --i)
{
uint8 tmp = out[i].r;
out[i].r = out[i].b;
out[i].b = tmp;
}
}
void GImage::BAYER_G8R8_B8G8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out)
{
// Run the equivalent function for red
BAYER_G8B8_R8G8_to_R8G8B8_MHC(w, h, in, _out);
// Now swap red and blue
swapRedAndBlue(w * h, (Color3uint8*)_out);
}
void GImage::BAYER_B8G8_G8R8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out)
{
// Run the equivalent function for red
BAYER_R8G8_G8B8_to_R8G8B8_MHC(w, h, in, _out);
// Now swap red and blue
swapRedAndBlue(w * h, (Color3uint8*)_out);
}
void GImage::BAYER_G8B8_R8G8_to_R8G8B8_MHC(int w, int h, const uint8* in, uint8* _out)
{
debugAssert(in != _out);
Color3uint8* out = (Color3uint8*)_out;
for (int y = 0; y < h; ++y)
{
// Row beginning in the input array.
int offset = y * w;
// GB row
for (int x = 0; x < w; ++x, ++out)
{
// G pixel
{
out->r = applyFilter(in, x, y, w, h, R_BGG);
out->g = in[x + offset];
out->b = applyFilter(in, x, y, w, h, B_BGG);
}
++x;
++out;
// B pixel
{
out->r = applyFilter(in, x, y, w, h, R_BGB);
out->g = applyFilter(in, x, y, w, h, G_BGB);
out->b = in[x + offset];
}
}
++y;
offset += w;
// RG row
for (int x = 0; x < w; ++x, ++out)
{
// R pixel
{
out->r = in[x + offset];
out->g = applyFilter(in, x, y, w, h, G_GRR);
out->b = applyFilter(in, x, y, w, h, B_GRR);
}
++x;
++out;
// G pixel
{
out->r = applyFilter(in, x, y, w, h, R_GRG);
out->g = in[x + offset];
out->b = applyFilter(in, x, y, w, h, B_GRG);
}
}
}
}
#undef B_BGG
#undef B_GRG
#undef B_GRR
} // namespace G3D

View File

@@ -0,0 +1,843 @@
/**
@file GImage_bmp.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-05-27
@edited 2006-05-10
*/
#include "platform.hpp"
#include "GImage.hpp"
#include "BinaryOutput.hpp"
namespace G3D
{
#ifndef G3D_WIN32
/**
This is used by the Windows bitmap I/O.
*/
static const int BI_RGB = 0;
#endif
void GImage::encodeBMP(BinaryOutput& out) const
{
debugAssert(m_channels == 1 || m_channels == 3);
out.setEndian(G3D_LITTLE_ENDIAN);
uint8 red;
uint8 green;
uint8 blue;
int pixelBufferSize = m_width * m_height * 3;
int fileHeaderSize = 14;
int infoHeaderSize = 40;
int BMScanWidth;
int BMPadding;
// First write the BITMAPFILEHEADER
//
// WORD bfType;
// DWORD bfSize;
// WORD bfReserved1;
// WORD bfReserved2;
// DWORD bfOffBits;
// Type
out.writeUInt8('B');
out.writeUInt8('M');
// File size
out.writeUInt32(fileHeaderSize + infoHeaderSize + pixelBufferSize);
// Two reserved fields set to zero
out.writeUInt16(0);
out.writeUInt16(0);
// The offset, in bytes, from the BITMAPFILEHEADER structure
// to the bitmap bits.
out.writeUInt32(infoHeaderSize + fileHeaderSize);
// Now the BITMAPINFOHEADER
//
// DWORD biSize;
// LONG biWidth;
// LONG biHeight;
// WORD biPlanes;
// WORD biBitCount
// DWORD biCompression;
// DWORD biSizeImage;
// LONG biXPelsPerMeter;
// LONG biYPelsPerMeter;
// DWORD biClrUsed;
// DWORD biClrImportant;
// Size of the info header
out.writeUInt32(infoHeaderSize);
// Width and height of the image
out.writeUInt32(m_width);
out.writeUInt32(m_height);
// Planes ("must be set to 1")
out.writeUInt16(1);
// BitCount and CompressionType
out.writeUInt16(24);
out.writeUInt32(BI_RGB);
// Image size ("may be zero for BI_RGB bitmaps")
out.writeUInt32(0);
// biXPelsPerMeter
out.writeUInt32(0);
// biYPelsPerMeter
out.writeUInt32(0);
// biClrUsed
out.writeUInt32(0);
// biClrImportant
out.writeUInt32(0);
BMScanWidth = m_width * 3;
if (BMScanWidth & 3)
{
BMPadding = 4 - (BMScanWidth & 3);
}
else
{
BMPadding = 0;
}
int hStart = m_height - 1;
int hEnd = -1;
int hDir = -1;
int dest;
// Write the pixel data
for (int h = hStart; h != hEnd; h += hDir)
{
dest = m_channels * h * m_width;
for (int w = 0; w < m_width; ++w)
{
if (m_channels == 3)
{
red = m_byte[dest];
green = m_byte[dest + 1];
blue = m_byte[dest + 2];
}
else
{
red = m_byte[dest];
green = m_byte[dest];
blue = m_byte[dest];
}
out.writeUInt8(blue);
out.writeUInt8(green);
out.writeUInt8(red);
dest += m_channels;
}
if (BMPadding > 0)
{
out.skip(BMPadding);
}
}
}
bool isDataSizeValid(int height, int paddedWidth, int headerSize, int palleteSize, int inputLength)
{
int64 dataSize = height * paddedWidth + headerSize + palleteSize;
return inputLength >= dataSize;
}
void GImage::decodeBMP(BinaryInput& input)
{
// The BMP decoding uses these flags.
static const uint16 PICTURE_NONE = 0x0000;
static const uint16 PICTURE_BITMAP = 0x1000;
// Compression Flags
static const uint16 PICTURE_UNCOMPRESSED = 0x0100;
static const uint16 PICTURE_MONOCHROME = 0x0001;
static const uint16 PICTURE_4BIT = 0x0002;
static const uint16 PICTURE_8BIT = 0x0004;
static const uint16 PICTURE_16BIT = 0x0008;
static const uint16 PICTURE_24BIT = 0x0010;
static const uint16 PICTURE_32BIT = 0x0020;
static const uint16 BMP_HEADERSIZE = 14;
(void)PICTURE_16BIT;
(void)PICTURE_32BIT;
// This is a simple BMP loader that can handle uncompressed BMP files.
input.reset();
if (input.getLength() < BMP_HEADERSIZE)
throw Error("BMB header corrupted - incomplete file");
// Verify this is a BMP file by looking for the BM tag.
std::string tag = input.readString(2);
if (tag != "BM")
{
throw Error("Not a supported BMP file");
}
m_channels = 3;
// Skip to the BITMAPINFOHEADER's width and height
input.skip(12);
int32 BMPInfoHeaderSize = input.readUInt32();
int32 wholeHeaderSize = BMP_HEADERSIZE + BMPInfoHeaderSize;
if (input.getLength() < wholeHeaderSize)
throw Error("BMP data header corrupted - incomplete file");
m_width = input.readUInt32();
m_height = input.readUInt32();
// Skip to the bit count and compression type
input.skip(2);
uint16 bitCount = input.readUInt16();
uint32 compressionType = input.readUInt32();
uint8 red;
uint8 green;
uint8 blue;
uint8 blank;
// Only uncompressed bitmaps are supported by this code
if ((int32)compressionType != BI_RGB)
{
throw Error("BMP images must be uncompressed");
}
uint8* palette = NULL;
uint32 paletteDataSize = 0;
// Create the palette if needed
if (bitCount <= 8)
{
// Skip to the palette color count in the header
input.skip(12);
int numColors = input.readUInt32();
// if numColor == 0 its full size pallete
if (numColors == 0)
switch (bitCount)
{
case 1:
numColors = 2;
break;
case 4:
numColors = 16;
break;
case 8:
numColors = 256;
break;
default:
numColors = 0;
break; // well, we know nothing about this crap. Its gonna fail lower in a code
}
paletteDataSize = numColors * 4;
palette = (uint8*)System::malloc(numColors * 3);
debugAssert(palette);
if (input.getLength() < wholeHeaderSize + paletteDataSize)
throw Error("BMB palette corruption - incomplete file");
// Skip past the end of the header to the palette info
input.skip(4);
int c;
for (c = 0; c < numColors * 3; c += 3)
{
// Palette information in bitmaps is stored in BGR_ format.
// That means it's blue-green-red-blank, for each entry.
blue = input.readUInt8();
green = input.readUInt8();
red = input.readUInt8();
blank = input.readUInt8();
palette[c] = red;
palette[c + 1] = green;
palette[c + 2] = blue;
}
}
int hStart = 0;
int hEnd = 0;
int hDir = 0;
if (m_height < 0)
{
m_height = -m_height;
hStart = 0;
hEnd = m_height;
hDir = 1;
}
else
{
// height = height;
hStart = m_height - 1;
hEnd = -1;
hDir = -1;
}
uint32 hSize = std::abs(hStart - hEnd);
m_byte = (uint8*)m_memMan->alloc(m_width * m_height * 3);
debugAssert(m_byte);
int BMScanWidth;
int BMPadding;
uint8 BMGroup;
uint8 BMPixel8;
int currPixel;
int dest;
int flags = PICTURE_NONE;
if (bitCount == 1)
{
// Note that this file is not necessarily grayscale, since it's possible
// the palette is blue-and-white, or whatever. But of course most image
// programs only write 1-bit images if they're black-and-white.
flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_MONOCHROME;
// For bitmaps, each scanline is dword-aligned.
BMScanWidth = (m_width + 7) >> 3;
if (BMScanWidth & 3)
{
BMScanWidth += 4 - (BMScanWidth & 3);
}
if (!isDataSizeValid(hSize, BMScanWidth, wholeHeaderSize, paletteDataSize, input.getLength()))
throw Error("BMP file corrupted");
// Powers of 2
int pow2[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
for (int h = hStart; h != hEnd; h += hDir)
{
currPixel = 0;
dest = 3 * h * m_width;
for (int w = 0; w < BMScanWidth; ++w)
{
BMGroup = input.readUInt8();
// Now we read the pixels. Usually there are eight pixels per byte,
// since each pixel is represented by one bit, but if the width
// is not a multiple of eight, the last byte will have some bits
// set, with the others just being extra. Plus there's the
// dword-alignment padding. So we keep checking to see if we've
// already read "width" number of pixels.
for (int i = 7; i >= 0; --i)
{
if (currPixel < m_width)
{
int src = 3 * ((BMGroup & pow2[i]) >> i);
m_byte[dest] = palette[src];
m_byte[dest + 1] = palette[src + 1];
m_byte[dest + 2] = palette[src + 2];
++currPixel;
dest += 3;
}
}
}
}
}
else if (bitCount == 4)
{
flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_4BIT;
// For bitmaps, each scanline is dword-aligned.
int BMScanWidth = (m_width + 1) >> 1;
if (BMScanWidth & 3)
{
BMScanWidth += 4 - (BMScanWidth & 3);
}
if (!isDataSizeValid(hSize, BMScanWidth, wholeHeaderSize, paletteDataSize, input.getLength()))
throw Error("BMP file corrupted");
for (int h = hStart; h != hEnd; h += hDir)
{
currPixel = 0;
dest = 3 * h * m_width;
for (int w = 0; w < BMScanWidth; w++)
{
BMGroup = input.readUInt8();
int src[2];
src[0] = 3 * ((BMGroup & 0xF0) >> 4);
src[1] = 3 * (BMGroup & 0x0F);
// Now we read the pixels. Usually there are two pixels per byte,
// since each pixel is represented by four bits, but if the width
// is not a multiple of two, the last byte will have only four bits
// set, with the others just being extra. Plus there's the
// dword-alignment padding. So we keep checking to see if we've
// already read "Width" number of pixels.
for (int i = 0; i < 2; ++i)
{
if (currPixel < m_width)
{
int tsrc = src[i];
m_byte[dest] = palette[tsrc];
m_byte[dest + 1] = palette[tsrc + 1];
m_byte[dest + 2] = palette[tsrc + 2];
++currPixel;
dest += 3;
}
}
}
}
}
else if (bitCount == 8)
{
flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_8BIT;
// For bitmaps, each scanline is dword-aligned.
BMScanWidth = m_width;
if (BMScanWidth & 3)
{
BMScanWidth += 4 - (BMScanWidth & 3);
}
if (!isDataSizeValid(hSize, BMScanWidth, wholeHeaderSize, paletteDataSize, input.getLength()))
throw Error("BMP file corrupted");
for (int h = hStart; h != hEnd; h += hDir)
{
currPixel = 0;
for (int w = 0; w < BMScanWidth; ++w)
{
BMPixel8 = input.readUInt8();
if (currPixel < m_width)
{
dest = 3 * ((h * m_width) + currPixel);
int src = 3 * BMPixel8;
m_byte[dest] = palette[src];
m_byte[dest + 1] = palette[src + 1];
m_byte[dest + 2] = palette[src + 2];
++currPixel;
}
}
}
}
else if (bitCount == 16)
{
m_memMan->free(m_byte);
m_byte = NULL;
System::free(palette);
palette = NULL;
throw Error("16-bit bitmaps not supported");
}
else if (bitCount == 24)
{
input.skip(20);
flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_24BIT;
// For bitmaps, each scanline is dword-aligned.
BMScanWidth = m_width * 3;
if (BMScanWidth & 3)
{
BMPadding = 4 - (BMScanWidth & 3);
}
else
{
BMPadding = 0;
}
if (!isDataSizeValid(hSize, BMScanWidth + BMPadding, wholeHeaderSize, paletteDataSize, input.getLength()))
throw Error("BMP file corrupted");
for (int h = hStart; h != hEnd; h += hDir)
{
dest = 3 * h * m_width;
for (int w = 0; w < m_width; ++w)
{
blue = input.readUInt8();
green = input.readUInt8();
red = input.readUInt8();
m_byte[dest] = red;
m_byte[dest + 1] = green;
m_byte[dest + 2] = blue;
dest += 3;
}
if (BMPadding)
{
input.skip(BMPadding);
}
}
}
else if (bitCount == 32)
{
m_memMan->free(m_byte);
m_byte = NULL;
System::free(palette);
palette = NULL;
throw Error("32 bit bitmaps not supported");
}
else
{
// We support all possible bit depths, so if the
// code gets here, it's not even a real bitmap.
m_memMan->free(m_byte);
m_byte = NULL;
throw Error("Not a bitmap!");
}
System::free(palette);
palette = NULL;
}
void GImage::decodeICO(BinaryInput& input)
{
// Header
uint16 r = input.readUInt16();
debugAssert(r == 0);
r = input.readUInt16();
debugAssert(r == 1);
// Read the number of icons, although we'll only load the
// first one.
int count = input.readUInt16();
m_channels = 4;
debugAssert(count > 0);
const uint8* headerBuffer = input.getCArray() + input.getPosition();
int maxWidth = 0, maxHeight = 0;
int maxHeaderNum = 0;
for (int currentHeader = 0; currentHeader < count; ++currentHeader)
{
const uint8* curHeaderBuffer = headerBuffer + (currentHeader * 16);
int tmpWidth = curHeaderBuffer[0];
int tmpHeight = curHeaderBuffer[1];
// Just in case there is a non-square icon, checking area
if ((tmpWidth * tmpHeight) > (maxWidth * maxHeight))
{
maxWidth = tmpWidth;
maxHeight = tmpHeight;
maxHeaderNum = currentHeader;
}
}
input.skip(maxHeaderNum * 16);
m_width = input.readUInt8();
m_height = input.readUInt8();
int numColors = input.readUInt8();
m_byte = (uint8*)m_memMan->alloc(m_width * m_height * m_channels);
debugAssert(m_byte);
// Bit mask for packed bits
int mask = 0;
int bitsPerPixel = 8;
switch (numColors)
{
case 2:
mask = 0x01;
bitsPerPixel = 1;
break;
case 16:
mask = 0x0F;
bitsPerPixel = 4;
break;
case 0:
numColors = 256;
mask = 0xFF;
bitsPerPixel = 8;
break;
default:
throw Error("Unsupported ICO color count.");
}
input.skip(5);
// Skip 'size' unused
input.skip(4);
int offset = input.readUInt32();
// Skip over any other icon descriptions
input.setPosition(offset);
// Skip over bitmap header; it is redundant
input.skip(40);
Array<Color4uint8> palette;
palette.resize(numColors, true);
for (int c = 0; c < numColors; ++c)
{
palette[c].b = input.readUInt8();
palette[c].g = input.readUInt8();
palette[c].r = input.readUInt8();
palette[c].a = input.readUInt8();
}
// The actual image and mask follow
// The XOR Bitmap is stored as 1-bit, 4-bit or 8-bit uncompressed Bitmap
// using the same encoding as BMP files. The AND Bitmap is stored in as
// 1-bit uncompressed Bitmap.
//
// Pixels are stored bottom-up, left-to-right. Pixel lines are padded
// with zeros to end on a 32bit (4byte) boundary. Every line will have the
// same number of bytes. Color indices are zero based, meaning a pixel color
// of 0 represents the first color table entry, a pixel color of 255 (if there
// are that many) represents the 256th entry.
/*
int bitsPerRow = width * bitsPerPixel;
int bytesPerRow = iCeil((double)bitsPerRow / 8);
// Rows are padded to 32-bit boundaries
bytesPerRow += bytesPerRow % 4;
// Read the XOR values into the color channel
for (int y = height - 1; y >= 0; --y) {
int x = 0;
// Read the row
for (int i = 0; i < bytesPerRow; ++i) {
uint8 byte = input.readUInt8();
for (int j = 0; (j < 8) && (x < width); ++x, j += bitsPerPixel) {
int bit = ((byte << j) >> (8 - bitsPerPixel)) & mask;
pixel4(x, y) = colorTable[bit];
}
}
}
*/
int hStart = 0;
int hEnd = 0;
int hDir = 0;
if (m_height < 0)
{
m_height = -m_height;
hStart = 0;
hEnd = m_height;
hDir = 1;
}
else
{
// height = height;
hStart = m_height - 1;
hEnd = -1;
hDir = -1;
}
int BMScanWidth;
uint8 BMGroup;
uint8 BMPixel8;
int currPixel;
int dest;
if (bitsPerPixel == 1)
{
// Note that this file is not necessarily grayscale, since it's possible
// the palette is blue-and-white, or whatever. But of course most image
// programs only write 1-bit images if they're black-and-white.
// For bitmaps, each scanline is dword-aligned.
BMScanWidth = (m_width + 7) >> 3;
if (BMScanWidth & 3)
{
BMScanWidth += 4 - (BMScanWidth & 3);
}
// Powers of 2
int pow2[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
for (int h = hStart; h != hEnd; h += hDir)
{
currPixel = 0;
dest = 3 * h * m_width;
for (int w = 0; w < BMScanWidth; ++w)
{
BMGroup = input.readUInt8();
// Now we read the pixels. Usually there are eight pixels per byte,
// since each pixel is represented by one bit, but if the width
// is not a multiple of eight, the last byte will have some bits
// set, with the others just being extra. Plus there's the
// dword-alignment padding. So we keep checking to see if we've
// already read "width" number of pixels.
for (int i = 7; i >= 0; --i)
{
if (currPixel < m_width)
{
int src = ((BMGroup & pow2[i]) >> i);
m_byte[dest] = palette[src].r;
m_byte[dest + 1] = palette[src].g;
m_byte[dest + 2] = palette[src].b;
++currPixel;
dest += 4;
}
}
}
}
}
else if (bitsPerPixel == 4)
{
// For bitmaps, each scanline is dword-aligned.
int BMScanWidth = (m_width + 1) >> 1;
if (BMScanWidth & 3)
{
BMScanWidth += 4 - (BMScanWidth & 3);
}
for (int h = hStart; h != hEnd; h += hDir)
{
currPixel = 0;
dest = 4 * h * m_width;
for (int w = 0; w < BMScanWidth; w++)
{
BMGroup = input.readUInt8();
int src[2];
src[0] = ((BMGroup & 0xF0) >> 4);
src[1] = (BMGroup & 0x0F);
// Now we read the pixels. Usually there are two pixels per byte,
// since each pixel is represented by four bits, but if the width
// is not a multiple of two, the last byte will have only four bits
// set, with the others just being extra. Plus there's the
// dword-alignment padding. So we keep checking to see if we've
// already read "Width" number of pixels.
for (int i = 0; i < 2; ++i)
{
if (currPixel < m_width)
{
int tsrc = src[i];
m_byte[dest] = palette[tsrc].r;
m_byte[dest + 1] = palette[tsrc].g;
m_byte[dest + 2] = palette[tsrc].b;
++currPixel;
dest += 4;
}
}
}
}
}
else if (bitsPerPixel == 8)
{
// For bitmaps, each scanline is dword-aligned.
BMScanWidth = m_width;
if (BMScanWidth & 3)
{
BMScanWidth += 4 - (BMScanWidth & 3);
}
for (int h = hStart; h != hEnd; h += hDir)
{
currPixel = 0;
for (int w = 0; w < BMScanWidth; ++w)
{
BMPixel8 = input.readUInt8();
if (currPixel < m_width)
{
dest = 4 * ((h * m_width) + currPixel);
int src = BMPixel8;
m_byte[dest] = palette[src].r;
m_byte[dest + 1] = palette[src].g;
m_byte[dest + 2] = palette[src].b;
++currPixel;
}
}
}
}
// Read the mask into the alpha channel
int bitsPerRow = m_width;
int bytesPerRow = iCeil((double)bitsPerRow / 8);
// For bitmaps, each scanline is dword-aligned.
// BMScanWidth = (width + 1) >> 1;
if (bytesPerRow & 3)
{
bytesPerRow += 4 - (bytesPerRow & 3);
}
for (int y = m_height - 1; y >= 0; --y)
{
int x = 0;
// Read the row
for (int i = 0; i < bytesPerRow; ++i)
{
uint8 byte = input.readUInt8();
for (int j = 0; (j < 8) && (x < m_width); ++x, ++j)
{
int bit = (byte >> (7 - j)) & 0x01;
pixel4(x, y).a = (1 - bit) * 0xFF;
}
}
}
}
} // namespace G3D

View File

@@ -0,0 +1,474 @@
/**
@file GImage_jpeg.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-05-27
@edited 2009-04-20
*/
#include "platform.hpp"
#include "GImage.hpp"
#include "BinaryOutput.hpp"
#include <sstream>
#include <cstring>
extern "C"
{
#ifdef G3D_LINUX
#include <jconfig.h>
#include <jpeglib.h>
#include <jerror.h>
#else
#include "jconfig.h"
#include "jpeglib.h"
#include "jerror.h"
#endif
}
namespace G3D
{
const int jpegQuality = 96;
/**
The IJG library needs special setup for compress/decompressing
from memory. These classes provide them.
The format of this class is defined by the IJG library; do not
change it.
*/
class memory_destination_mgr
{
public:
struct jpeg_destination_mgr pub;
JOCTET* buffer;
int size;
int count;
};
typedef memory_destination_mgr* mem_dest_ptr;
/**
Signature dictated by IJG.
*/
static void init_destination(j_compress_ptr cinfo)
{
mem_dest_ptr dest = (mem_dest_ptr)cinfo->dest;
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = dest->size;
dest->count = 0;
}
/**
Signature dictated by IJG.
*/
static boolean empty_output_buffer(j_compress_ptr cinfo)
{
mem_dest_ptr dest = (mem_dest_ptr)cinfo->dest;
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = dest->size;
return TRUE;
}
/**
Signature dictated by IJG.
*/
static void term_destination(j_compress_ptr cinfo)
{
mem_dest_ptr dest = (mem_dest_ptr)cinfo->dest;
dest->count = dest->size - dest->pub.free_in_buffer;
}
/**
Signature dictated by IJG.
*/
static void jpeg_memory_dest(j_compress_ptr cinfo, JOCTET* buffer, int size)
{
mem_dest_ptr dest;
if (cinfo->dest == NULL)
{
// First time for this JPEG object; call the
// IJG allocator to get space.
cinfo->dest = (struct jpeg_destination_mgr*)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(memory_destination_mgr));
}
dest = (mem_dest_ptr)cinfo->dest;
dest->size = size;
dest->buffer = buffer;
dest->pub.init_destination = init_destination;
dest->pub.empty_output_buffer = empty_output_buffer;
dest->pub.term_destination = term_destination;
}
////////////////////////////////////////////////////////////////////////////////////////
#define INPUT_BUF_SIZE 4096
/**
Structure dictated by IJG.
*/
class memory_source_mgr
{
public:
struct jpeg_source_mgr pub;
int source_size;
unsigned char* source_data;
boolean start_of_data;
JOCTET* buffer;
};
typedef memory_source_mgr* mem_src_ptr;
/**
Signature dictated by IJG.
*/
static void init_source(j_decompress_ptr cinfo)
{
mem_src_ptr src = (mem_src_ptr)cinfo->src;
src->start_of_data = TRUE;
}
/**
Signature dictated by IJG.
*/
static boolean fill_input_buffer(j_decompress_ptr cinfo)
{
mem_src_ptr src = (mem_src_ptr)cinfo->src;
size_t bytes_read = 0;
if (src->source_size > INPUT_BUF_SIZE)
bytes_read = INPUT_BUF_SIZE;
else
bytes_read = src->source_size;
if (bytes_read <= 0)
{
if (src->start_of_data) // Treat empty input file as fatal error
ERREXIT(cinfo, JERR_INPUT_EMPTY);
WARNMS(cinfo, JWRN_JPEG_EOF);
// Insert a fake EOI marker
src->buffer[0] = (JOCTET)0xFF;
src->buffer[1] = (JOCTET)JPEG_EOI;
bytes_read = 2;
}
else
memcpy(src->buffer, src->source_data, bytes_read);
src->source_data += bytes_read;
if (src->source_size >= bytes_read)
src->source_size -= bytes_read;
else
src->source_size = 0;
src->pub.next_input_byte = src->buffer;
src->pub.bytes_in_buffer = bytes_read;
src->start_of_data = FALSE;
return TRUE;
}
/**
Signature dictated by IJG.
*/
static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
mem_src_ptr src = (mem_src_ptr)cinfo->src;
if (num_bytes > 0)
{
while (num_bytes > (long)src->pub.bytes_in_buffer)
{
num_bytes -= (long)src->pub.bytes_in_buffer;
boolean s = fill_input_buffer(cinfo);
debugAssert(s);
(void)s;
}
src->pub.next_input_byte += (size_t)num_bytes;
src->pub.bytes_in_buffer -= (size_t)num_bytes;
}
}
/**
Signature dictated by IJG.
*/
static void term_source(j_decompress_ptr cinfo)
{
(void)cinfo;
// Intentionally empty
}
/**
Signature dictated by IJG.
*/
static void jpeg_memory_src(j_decompress_ptr cinfo, JOCTET* buffer, int size)
{
mem_src_ptr src;
if (cinfo->src == NULL)
{
// First time for this JPEG object
cinfo->src = (struct jpeg_source_mgr*)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(memory_source_mgr));
src = (mem_src_ptr)cinfo->src;
src->buffer = (JOCTET*)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, INPUT_BUF_SIZE * sizeof(JOCTET));
}
src = (mem_src_ptr)cinfo->src;
src->pub.init_source = init_source;
src->pub.fill_input_buffer = fill_input_buffer;
src->pub.skip_input_data = skip_input_data;
// use default method
src->pub.resync_to_restart = jpeg_resync_to_restart;
src->pub.term_source = term_source;
src->source_data = buffer;
src->source_size = size;
// forces fill_input_buffer on first read
src->pub.bytes_in_buffer = 0;
// until buffer loaded
src->pub.next_input_byte = NULL;
}
void GImage::encodeJPEG(BinaryOutput& out) const
{
if (m_channels != 3)
{
// Convert to three channel
GImage tmp = *this;
tmp.convertToRGB();
tmp.encodeJPEG(out);
return;
}
debugAssert(m_channels == 3);
out.setEndian(G3D_LITTLE_ENDIAN);
// Allocate and initialize a compression object
jpeg_compress_struct cinfo;
jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
// Specify the destination for the compressed data.
// (Overestimate the size)
int buffer_size = m_width * m_height * 3 + 200;
JOCTET* compressed_data = (JOCTET*)System::malloc(buffer_size);
jpeg_memory_dest(&cinfo, compressed_data, buffer_size);
cinfo.image_width = m_width;
cinfo.image_height = m_height;
// # of color components per pixel
cinfo.input_components = 3;
// colorspace of input image
cinfo.in_color_space = JCS_RGB;
cinfo.input_gamma = 1.0;
// Set parameters for compression, including image size & colorspace
jpeg_set_defaults(&cinfo);
#if !defined(__linux) && !defined(__APPLE__)
jpeg_set_quality(&cinfo, jpegQuality, false);
#else
jpeg_set_quality(&cinfo, jpegQuality, FALSE);
#endif
cinfo.smoothing_factor = 0;
cinfo.optimize_coding = TRUE;
// cinfo.dct_method = JDCT_FLOAT;
cinfo.dct_method = JDCT_ISLOW;
cinfo.jpeg_color_space = JCS_YCbCr;
// Initialize the compressor
jpeg_start_compress(&cinfo, TRUE);
// Iterate over all scanlines from top to bottom
// pointer to a single row
JSAMPROW row_pointer[1];
// JSAMPLEs per row in image_buffer
int row_stride = cinfo.image_width * 3;
while (cinfo.next_scanline < cinfo.image_height)
{
row_pointer[0] = &(m_byte[cinfo.next_scanline * row_stride]);
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
// Shut down the compressor
jpeg_finish_compress(&cinfo);
// Figure out how big the result was.
int outLength = ((mem_dest_ptr)cinfo.dest)->count;
// Release the JPEG compression object
jpeg_destroy_compress(&cinfo);
// Copy into an appropriately sized output buffer.
out.writeBytes(compressed_data, outLength);
// Free the conservative buffer.
System::free(compressed_data);
compressed_data = NULL;
}
void GImage::decodeJPEG(BinaryInput& input)
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
int loc = 0;
m_channels = 3;
// We have to set up the error handler, in case initialization fails.
cinfo.err = jpeg_std_error(&jerr);
// Initialize the JPEG decompression object.
jpeg_create_decompress(&cinfo);
// Specify data source (eg, a file, for us, memory)
jpeg_memory_src(&cinfo, const_cast<uint8*>(input.getCArray()), input.size());
// Read the parameters with jpeg_read_header()
int ret = jpeg_read_header(&cinfo, FALSE);
if (ret != JPEG_HEADER_OK)
throw Error("Problem reading file header.");
// Set parameters for decompression
// (We do nothing here since the defaults are fine)
// Start decompressor
if (!jpeg_start_decompress(&cinfo))
throw Error("Problem decompressing file.");
// Get and set the values of interest to this object
m_width = cinfo.output_width;
m_height = cinfo.output_height;
// Prepare the pointer object for the pixel data
size_t bytesToAlloc = m_width * m_height * 3;
m_byte = (uint8*)m_memMan->alloc(bytesToAlloc);
if (!m_byte)
{
std::stringstream ss;
ss << "Out of memory while allocating " << bytesToAlloc << " bytes";
throw GImage::Error(ss.str());
}
// JSAMPLEs per row in output buffer
int bpp = cinfo.output_components;
int row_stride = cinfo.output_width * bpp;
// Make a one-row-high sample array that will go away when done with image
JSAMPARRAY temp = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
// Read data on a scanline by scanline basis
while (cinfo.output_scanline < cinfo.output_height)
{
// We may need to adjust the output based on the
// number of channels it has.
switch (bpp)
{
case 1:
// Grayscale; decompress to temp.
jpeg_read_scanlines(&cinfo, temp, 1);
// Expand to three channels
{
uint8* scan = &(m_byte[loc * 3]);
uint8* endScan = scan + (m_width * 3);
uint8* t = *temp;
while (scan < endScan)
{
uint8 value = t[0];
// Spread the value 3x.
scan[0] = value;
scan[1] = value;
scan[2] = value;
scan += 3;
t += 1;
}
}
break;
case 3:
// Read directly into the array
{
// Need one extra level of indirection.
uint8* scan = m_byte + loc;
JSAMPARRAY ptr = &scan;
jpeg_read_scanlines(&cinfo, ptr, 1);
}
break;
case 4:
// RGBA; decompress to temp.
jpeg_read_scanlines(&cinfo, temp, 1);
// Drop the 3rd channel
{
uint8* scan = &(m_byte[loc * 3]);
uint8* endScan = scan + m_width * 3;
uint8* t = *temp;
while (scan < endScan)
{
scan[0] = t[0];
scan[1] = t[1];
scan[2] = t[2];
scan += 3;
t += 4;
}
}
break;
default:
throw Error("Unexpected number of channels.");
}
loc += row_stride;
}
// Finish decompression
jpeg_finish_decompress(&cinfo);
alwaysAssertM(this, "Corrupt GImage");
// Release JPEG decompression object
jpeg_destroy_decompress(&cinfo);
}
} // namespace G3D

View File

@@ -0,0 +1,190 @@
/**
@file GImage_jxl.cpp
@author PATRICK STAR
@created 2025
*/
#include "GImage.hpp"
#include "BinaryOutput.hpp"
#include "platform.hpp"
#include <jxl/encode.h>
#include <jxl/decode.h>
#include <jxl/thread_parallel_runner.h>
#include <jxl/memory_manager.h>
#include <stdexcept>
#include <vector>
namespace G3D
{
void GImage::encodeJXL(BinaryOutput& out) const
{
if (!(m_channels == 1 || m_channels == 3 || m_channels == 4))
{
throw GImage::Error("Unsupported number of channels for JXL.");
}
if (m_width <= 0 || m_height <= 0)
{
throw GImage::Error("Invalid dimensions for JXL.");
}
JxlEncoder* encoder = JxlEncoderCreate(nullptr);
if (!encoder)
{
throw GImage::Error("Failed to create JXL encoder.");
}
void* runner = JxlThreadParallelRunnerCreate(nullptr, JxlThreadParallelRunnerDefaultNumWorkerThreads());
if (!runner)
{
JxlEncoderDestroy(encoder);
throw GImage::Error("Failed to create JXL thread runner.");
}
JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner);
// Create frame settings
JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(encoder, nullptr);
if (!frame_settings)
{
JxlEncoderDestroy(encoder);
JxlThreadParallelRunnerDestroy(runner);
throw GImage::Error("Failed to create JXL frame settings.");
}
JxlPixelFormat pixel_format = {static_cast<uint32_t>(m_channels), JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
if (JxlEncoderAddImageFrame(frame_settings, &pixel_format, m_byte, m_width * m_height * m_channels) != JXL_ENC_SUCCESS)
{
JxlEncoderDestroy(encoder);
JxlThreadParallelRunnerDestroy(runner);
throw GImage::Error("Failed to add image frame to JXL encoder.");
}
JxlEncoderCloseInput(encoder);
std::vector<uint8_t> output(4096);
uint8_t* next_out = output.data();
size_t avail_out = output.size();
JxlEncoderStatus status;
while ((status = JxlEncoderProcessOutput(encoder, &next_out, &avail_out)) == JXL_ENC_NEED_MORE_OUTPUT)
{
size_t offset = output.size() - avail_out;
output.resize(output.size() * 2);
next_out = output.data() + offset;
avail_out = output.size() - offset;
}
if (status != JXL_ENC_SUCCESS)
{
JxlEncoderDestroy(encoder);
JxlThreadParallelRunnerDestroy(runner);
throw GImage::Error("Error during JXL encoding.");
}
out.writeBytes(output.data(), output.size() - avail_out);
JxlEncoderDestroy(encoder);
JxlThreadParallelRunnerDestroy(runner);
}
void GImage::decodeJXL(BinaryInput& input)
{
std::vector<uint8_t> input_data(input.getLength());
input.readBytes(input_data.data(), input_data.size());
JxlDecoder* decoder = JxlDecoderCreate(nullptr);
if (!decoder)
{
throw GImage::Error("Failed to create JXL decoder.");
}
// Subscribe to the events we need
if (JxlDecoderSubscribeEvents(decoder, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS)
{
JxlDecoderDestroy(decoder);
throw GImage::Error("Failed to subscribe to JXL decoder events.");
}
if (JxlDecoderSetInput(decoder, input_data.data(), input_data.size()) != JXL_DEC_SUCCESS)
{
JxlDecoderDestroy(decoder);
throw GImage::Error("Failed to set JXL input data.");
}
JxlDecoderCloseInput(decoder);
JxlBasicInfo info;
JxlPixelFormat pixel_format;
bool info_retrieved = false;
bool buffer_set = false;
// Process decoder events
JxlDecoderStatus status;
while ((status = JxlDecoderProcessInput(decoder)) != JXL_DEC_SUCCESS)
{
if (status == JXL_DEC_BASIC_INFO)
{
if (JxlDecoderGetBasicInfo(decoder, &info) != JXL_DEC_SUCCESS)
{
JxlDecoderDestroy(decoder);
throw GImage::Error("Failed to get JXL basic info.");
}
m_width = info.xsize;
m_height = info.ysize;
m_channels = (info.alpha_bits > 0) ? 4 : 3;
info_retrieved = true;
}
else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER)
{
if (!info_retrieved)
{
JxlDecoderDestroy(decoder);
throw GImage::Error("Need output buffer before basic info retrieved.");
}
pixel_format = {static_cast<uint32_t>(m_channels), JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
m_byte = (uint8_t*)m_memMan->alloc(m_width * m_height * m_channels);
if (!m_byte)
{
JxlDecoderDestroy(decoder);
throw GImage::Error("Failed to allocate memory for decoded JXL image.");
}
if (JxlDecoderSetImageOutBuffer(decoder, &pixel_format, m_byte, m_width * m_height * m_channels) != JXL_DEC_SUCCESS)
{
JxlDecoderDestroy(decoder);
throw GImage::Error("Failed to set JXL output buffer.");
}
buffer_set = true;
}
else if (status == JXL_DEC_FULL_IMAGE)
{
// Image successfully decoded
break;
}
else if (status == JXL_DEC_ERROR)
{
JxlDecoderDestroy(decoder);
throw GImage::Error("Error during JXL decoding.");
}
else
{
JxlDecoderDestroy(decoder);
throw GImage::Error("Unexpected JXL decoder status.");
}
}
if (!info_retrieved || !buffer_set)
{
JxlDecoderDestroy(decoder);
throw GImage::Error("JXL decoding completed without retrieving all necessary data.");
}
JxlDecoderDestroy(decoder);
}
} // namespace G3D

View File

@@ -0,0 +1,333 @@
/**
@file GImage_png.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-05-27
@edited 2009-04-20
*/
#include "platform.hpp"
#include "GImage.hpp"
#include <png.h>
#include <sstream>
#include "BinaryOutput.hpp"
namespace G3D
{
// libpng required function signature
static void png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
// d9mz - would anybody even debug this? debugAssert(png_ptr->io_ptr != NULL);
// d9mz - would anybody even debug this? debugAssert(data != NULL);
((BinaryOutput*)png_get_io_ptr(png_ptr))->writeBytes(data, length);
}
// libpng required function signature
static void png_flush_data(png_structp png_ptr)
{
(void)png_ptr;
// Do nothing.
}
// libpng required function signature
static void png_error(png_structp png_ptr, png_const_charp error_msg)
{
(void)png_ptr;
// d9mz - would anybody even debug this? debugAssert(error_msg != NULL);
// png_ptr->error_ptr = const_cast<png_charp>(error_msg);
png_longjmp(png_ptr, 1);
}
// libpng required function signature
void png_warning(png_structp png_ptr, png_const_charp warning_msg) {}
// libpng required function signature
static void png_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
// d9mz - would anybody even debug this? debugAssert(png_ptr->io_ptr != NULL);
// d9mz - would anybody even debug this? debugAssert(length >= 0);
// d9mz - would anybody even debug this? debugAssert(data != NULL);
int64 pos = ((BinaryInput*)png_get_io_ptr(png_ptr))->getPosition();
int64 binaryOutputLen = ((BinaryInput*)png_get_io_ptr(png_ptr))->getLength();
if (pos + length > binaryOutputLen)
{
length = binaryOutputLen - pos;
G3D::png_error(png_ptr, "Trying to load incomplete image");
}
((BinaryInput*)png_get_io_ptr(png_ptr))->readBytes(data, length);
}
void GImage::encodePNG(BinaryOutput& out) const
{
if (!(m_channels == 1 || m_channels == 2 || m_channels == 3 || m_channels == 4))
{
throw GImage::Error(format("Illegal channels for PNG: %d", m_channels));
}
if (m_width <= 0)
{
throw GImage::Error(format("Illegal width for PNG: %d", m_width));
}
if (m_height <= 0)
{
throw GImage::Error(format("Illegal height for PNG: %d", m_height));
}
// PNG library requires that the height * pointer size fit within an int
if (png_uint_32(m_height) * sizeof(png_bytep) > PNG_UINT_32_MAX)
{
throw GImage::Error("Unsupported PNG height.");
}
out.setEndian(G3D_LITTLE_ENDIAN);
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, png_error, png_warning);
if (!png_ptr)
{
throw GImage::Error("Unable to initialize PNG encoder.");
}
if (setjmp(png_jmpbuf(png_ptr)))
{
const char* message = png_get_error_ptr(png_ptr) ? static_cast<const char*>(png_get_error_ptr(png_ptr)) : "Unknown PNG error";
throw GImage::Error(message);
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
png_destroy_write_struct(&png_ptr, &info_ptr);
throw GImage::Error("Unable to initialize PNG encoder.");
}
// setup libpng write handler so can use BinaryOutput
png_set_write_fn(png_ptr, (void*)&out, png_write_data, png_flush_data);
png_color_8_struct sig_bit;
switch (m_channels)
{
case 1:
png_set_IHDR(
png_ptr, info_ptr, m_width, m_height, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
sig_bit.red = 0;
sig_bit.green = 0;
sig_bit.blue = 0;
sig_bit.alpha = 0;
sig_bit.gray = 8;
break;
case 2:
png_set_IHDR(
png_ptr, info_ptr, m_width, m_height, 8, PNG_COLOR_TYPE_GRAY_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
sig_bit.red = 0;
sig_bit.green = 0;
sig_bit.blue = 0;
sig_bit.alpha = 8;
sig_bit.gray = 8;
break;
case 3:
png_set_IHDR(
png_ptr, info_ptr, m_width, m_height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
sig_bit.red = 8;
sig_bit.green = 8;
sig_bit.blue = 8;
sig_bit.alpha = 0;
sig_bit.gray = 0;
break;
case 4:
png_set_IHDR(
png_ptr, info_ptr, m_width, m_height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
sig_bit.red = 8;
sig_bit.green = 8;
sig_bit.blue = 8;
sig_bit.alpha = 8;
sig_bit.gray = 0;
break;
default:
png_destroy_write_struct(&png_ptr, &info_ptr);
throw GImage::Error("Unsupported number of channels for PNG.");
}
png_set_sBIT(png_ptr, info_ptr, &sig_bit);
// write the png header
png_write_info(png_ptr, info_ptr);
png_bytepp row_pointers = new png_bytep[m_height];
for (int i = 0; i < m_height; ++i)
{
row_pointers[i] = (png_bytep)&m_byte[m_width * m_channels * i];
}
png_write_image(png_ptr, row_pointers);
png_write_end(png_ptr, info_ptr);
delete[] row_pointers;
png_destroy_write_struct(&png_ptr, &info_ptr);
}
void GImage::decodePNG(BinaryInput& input)
{
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, png_error, png_warning);
if (png_ptr == NULL)
{
throw GImage::Error("Unable to initialize PNG decoder.");
}
#if !defined(__linux) && !defined(__APPLE__)
if (setjmp(png_jmpbuf(png_ptr)))
{
const char* message = png_get_error_ptr(png_ptr) ? static_cast<const char*>(png_get_error_ptr(png_ptr)) : "Unknown PNG error";
throw GImage::Error(ERROR);
}
#else
if (setjmp(png_jmpbuf(png_ptr)))
{
const char* message = png_get_error_ptr(png_ptr) ? static_cast<const char*>(png_get_error_ptr(png_ptr)) : "Unknown PNG error";
// Correctly use the 'message' variable to throw an error
throw GImage::Error(message);
}
#endif
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
{
png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
throw GImage::Error("Unable to initialize PNG decoder.");
}
png_infop end_info = png_create_info_struct(png_ptr);
if (end_info == NULL)
{
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
throw GImage::Error("Unable to initialize PNG decoder.");
}
// now that the libpng structures are setup, change the error handlers and read routines
// to use G3D functions so that BinaryInput can be used.
png_set_read_fn(png_ptr, (png_voidp)&input, png_read_data);
// read in sequentially so that three copies of the file are not in memory at once
png_read_info(png_ptr, info_ptr);
png_uint_32 png_width, png_height;
int bit_depth, color_type, interlace_type;
// this will validate the data it extracts from info_ptr
png_get_IHDR(png_ptr, info_ptr, &png_width, &png_height, &bit_depth, &color_type, &interlace_type, NULL, NULL);
m_width = static_cast<uint32>(png_width);
m_height = static_cast<uint32>(png_height);
// swap bytes of 16 bit files to least significant byte first
png_set_swap(png_ptr);
png_set_strip_16(png_ptr);
// Expand paletted colors into true RGB triplets
if (color_type == PNG_COLOR_TYPE_PALETTE)
{
png_set_palette_to_rgb(png_ptr);
}
// Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
{
png_set_expand(png_ptr);
}
// Expand paletted or RGB images with transparency to full alpha channels
// so the data will be available as RGBA quartets.
bool has1BitAlpha = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
if (has1BitAlpha)
{
png_set_tRNS_to_alpha(png_ptr);
}
// Fix sub-8 bit_depth to 8bit
if (bit_depth < 8)
{
png_set_packing(png_ptr);
}
size_t bytesToAlloc = 0;
png_bytep trans;
int num_trans;
png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
if ((color_type == PNG_COLOR_TYPE_RGBA) || ((color_type == PNG_COLOR_TYPE_PALETTE) && (num_trans > 0)) ||
(color_type == PNG_COLOR_TYPE_RGB && has1BitAlpha))
{
m_channels = 4;
}
else if ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_PALETTE))
{
m_channels = 3;
}
else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA || (color_type == PNG_COLOR_TYPE_GRAY && has1BitAlpha))
{
m_channels = 2;
}
else if (color_type == PNG_COLOR_TYPE_GRAY)
{
m_channels = 1;
}
else
{
throw GImage::Error("Unsupported PNG bit-depth or type.");
}
bytesToAlloc = m_width * m_height * m_channels;
m_byte = (uint8*)m_memMan->alloc(bytesToAlloc);
if (!m_byte)
{
std::stringstream ss;
ss << "Out of memory while allocating " << bytesToAlloc << " bytes";
throw GImage::Error(ss.str());
}
// since we are reading row by row, required to handle interlacing
uint32 number_passes = png_set_interlace_handling(png_ptr);
png_read_update_info(png_ptr, info_ptr);
for (uint32 pass = 0; pass < number_passes; ++pass)
{
for (uint32 y = 0; y < (uint32)m_height; ++y)
{
png_bytep rowPointer = &m_byte[m_width * m_channels * y];
png_read_rows(png_ptr, &rowPointer, NULL, 1);
}
}
png_read_end(png_ptr, info_ptr);
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
}
} // namespace G3D

View File

@@ -0,0 +1,284 @@
/**
@file GImage_tga.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-05-27
@edited 2009-05-10
*/
#include "platform.hpp"
#include "GImage.hpp"
#include "BinaryOutput.hpp"
namespace G3D
{
void GImage::encodeTGA(BinaryOutput& out) const
{
out.setEndian(G3D_LITTLE_ENDIAN);
// ID length
out.writeUInt8(0);
// Color map Type
out.writeUInt8(0);
// Type
out.writeUInt8(2);
// Color map
out.skip(5);
// x, y offsets
out.writeUInt16(0);
out.writeUInt16(0);
// Width & height
out.writeUInt16(m_width);
out.writeUInt16(m_height);
// Color depth
if (m_channels == 1)
{
// Force RGB mode
out.writeUInt8(8 * 3);
}
else
{
out.writeUInt8(8 * m_channels);
}
// Image descriptor
if (m_channels < 4)
{
// 0 alpha bits
out.writeUInt8(0);
}
else
{
// 8 alpha bits
out.writeUInt8(8);
}
// Image ID (zero length)
if (m_channels == 1)
{
// Pixels are upside down in BGR format.
for (int y = m_height - 1; y >= 0; --y)
{
for (int x = 0; x < m_width; ++x)
{
uint8 p = (m_byte[(y * m_width + x)]);
out.writeUInt8(p);
out.writeUInt8(p);
out.writeUInt8(p);
}
}
}
else if (m_channels == 3)
{
// Pixels are upside down in BGR format.
for (int y = m_height - 1; y >= 0; --y)
{
for (int x = 0; x < m_width; ++x)
{
uint8* p = &(m_byte[3 * (y * m_width + x)]);
out.writeUInt8(p[2]);
out.writeUInt8(p[1]);
out.writeUInt8(p[0]);
}
}
}
else
{
// Pixels are upside down in BGRA format.
for (int y = m_height - 1; y >= 0; --y)
{
for (int x = 0; x < m_width; ++x)
{
uint8* p = &(m_byte[4 * (y * m_width + x)]);
out.writeUInt8(p[2]);
out.writeUInt8(p[1]);
out.writeUInt8(p[0]);
out.writeUInt8(p[3]);
}
}
}
// Write "TRUEVISION-XFILE " 18 bytes from the end
// (with null termination)
out.writeString("TRUEVISION-XFILE ");
}
inline static void readBGR(uint8* byte, BinaryInput& bi)
{
int b = bi.readUInt8();
int g = bi.readUInt8();
int r = bi.readUInt8();
byte[0] = r;
byte[1] = g;
byte[2] = b;
}
inline static void readBGRA(uint8* byte, BinaryInput& bi)
{
readBGR(byte, bi);
byte[3] = bi.readUInt8();
}
void GImage::decodeTGA(BinaryInput& input)
{
// This is a simple TGA loader that can handle uncompressed
// truecolor TGA files (TGA type 2).
// Verify this is a TGA file by looking for the TRUEVISION tag.
int pos = input.getPosition();
input.setPosition(input.size() - 18);
std::string tag = input.readString(16);
if (tag != "TRUEVISION-XFILE")
{
throw Error("Not a TGA file");
}
input.setPosition(pos);
int IDLength = input.readUInt8();
int colorMapType = input.readUInt8();
int imageType = input.readUInt8();
(void)colorMapType;
// 2 is the type supported by this routine.
if (imageType != 2 && imageType != 10)
{
throw Error("TGA images must be type 2 (Uncompressed truecolor) or 10 (Run-length truecolor)");
}
// Color map specification
input.skip(5);
// Image specification
// Skip x and y offsets
input.skip(4);
m_width = input.readInt16();
m_height = input.readInt16();
int colorDepth = input.readUInt8();
if ((colorDepth != 24) && (colorDepth != 32))
{
throw Error("TGA files must be 24 or 32 bit.");
}
if (colorDepth == 32)
{
m_channels = 4;
}
else
{
m_channels = 3;
}
// Image descriptor contains overlay data as well
// as data indicating where the origin is
int imageDescriptor = input.readUInt8();
(void)imageDescriptor;
// Image ID
input.skip(IDLength);
m_byte = (uint8*)m_memMan->alloc(m_width * m_height * m_channels);
debugAssert(m_byte);
// Pixel data
int x;
int y;
if (imageType == 2)
{
// Uncompressed
if (m_channels == 3)
{
for (y = m_height - 1; y >= 0; --y)
{
for (x = 0; x < m_width; ++x)
{
int i = (x + y * m_width) * 3;
readBGR(m_byte + i, input);
}
}
}
else
{
for (y = m_height - 1; y >= 0; --y)
{
for (x = 0; x < m_width; ++x)
{
int i = (x + y * m_width) * 4;
readBGRA(m_byte + i, input);
}
}
}
}
else if (imageType == 10)
{
// Run-length encoded
for (y = m_height - 1; y >= 0; --y)
{
for (int x = 0; x < m_width; /* intentionally no x increment */)
{
// The specification guarantees that no packet will wrap past the end of a row
const uint8 repetitionCount = input.readUInt8();
const uint8 numValues = (repetitionCount & (~128)) + 1;
int byteOffset = (x + y * m_width) * 3;
if (repetitionCount & 128)
{
// When the high bit is 1, this is a run-length packet
if (m_channels == 3)
{
Color3uint8 value;
readBGR((uint8*)(&value), input);
for (int i = 0; i < numValues; ++i, ++x)
{
for (int b = 0; b < 3; ++b, ++byteOffset)
{
m_byte[byteOffset] = value[b];
}
}
}
else
{
Color4uint8 value;
readBGRA((uint8*)(&value), input);
for (int i = 0; i < numValues; ++i, ++x)
{
for (int b = 0; b < 3; ++b, ++byteOffset)
{
m_byte[byteOffset] = value[b];
}
}
}
}
else
{
// When the high bit is 0, this is a raw packet
for (int i = 0; i < numValues; ++i, ++x, byteOffset += m_channels)
{
readBGR(m_byte + byteOffset, input);
}
}
}
}
}
else
{
alwaysAssertM(false, "Unsupported type");
}
}
} // namespace G3D

205
engine/3d/src/GLight.cpp Normal file
View File

@@ -0,0 +1,205 @@
/**
@file GLight.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-11-12
@edited 2009-11-16
*/
#include "GLight.hpp"
#include "Sphere.hpp"
#include "CoordinateFrame.hpp"
#include "stringutils.hpp"
namespace G3D
{
GLight::GLight()
: position(0, 0, 0, 0)
, rightDirection(0, 0, 0)
, spotDirection(0, 0, -1)
, spotCutoff(180)
, spotSquare(false)
, color(Color3::white())
, enabled(false)
, specular(true)
, diffuse(true)
{
attenuation[0] = 1.0;
attenuation[1] = 0.0;
attenuation[2] = 0.0;
}
GLight GLight::directional(const Vector3& toLight, const Color3& color, bool s, bool d)
{
GLight L;
L.position = Vector4(toLight.direction(), 0);
L.color = color;
L.specular = s;
L.diffuse = d;
return L;
}
GLight GLight::point(const Vector3& pos, const Color3& color, float constAtt, float linAtt, float quadAtt, bool s, bool d)
{
GLight L;
L.position = Vector4(pos, 1);
L.color = color;
L.attenuation[0] = constAtt;
L.attenuation[1] = linAtt;
L.attenuation[2] = quadAtt;
L.specular = s;
L.diffuse = d;
return L;
}
GLight GLight::spot(const Vector3& pos, const Vector3& pointDirection, float cutOffAngleDegrees, const Color3& color, float constAtt, float linAtt,
float quadAtt, bool s, bool d)
{
GLight L;
L.position = Vector4(pos, 1.0f);
L.spotDirection = pointDirection.direction();
debugAssert(cutOffAngleDegrees <= 90);
L.spotCutoff = cutOffAngleDegrees;
L.color = color;
L.attenuation[0] = constAtt;
L.attenuation[1] = linAtt;
L.attenuation[2] = quadAtt;
L.specular = s;
L.diffuse = d;
return L;
}
bool GLight::operator==(const GLight& other) const
{
return (position == other.position) && (rightDirection == other.rightDirection) && (spotDirection == other.spotDirection) &&
(spotCutoff == other.spotCutoff) && (spotSquare == other.spotSquare) && (attenuation[0] == other.attenuation[0]) &&
(attenuation[1] == other.attenuation[1]) && (attenuation[2] == other.attenuation[2]) && (color == other.color) &&
(enabled == other.enabled) && (specular == other.specular) && (diffuse == other.diffuse);
}
bool GLight::operator!=(const GLight& other) const
{
return !(*this == other);
}
Sphere GLight::effectSphere(float cutoff) const
{
if (position.w == 0)
{
// Directional light
return Sphere(Vector3::zero(), finf());
}
else
{
// Avoid divide by zero
cutoff = max(cutoff, 0.00001f);
float maxIntensity = max(color.r, max(color.g, color.b));
float radius = finf();
if (attenuation[2] != 0)
{
// Solve I / attenuation.dot(1, r, r^2) < cutoff for r
//
// a[0] + a[1] r + a[2] r^2 > I/cutoff
//
float a = attenuation[2];
float b = attenuation[1];
float c = attenuation[0] - maxIntensity / cutoff;
float discrim = square(b) - 4 * a * c;
if (discrim >= 0)
{
discrim = sqrt(discrim);
float r1 = (-b + discrim) / (2 * a);
float r2 = (-b - discrim) / (2 * a);
if (r1 < 0)
{
if (r2 > 0)
{
radius = r2;
}
}
else if (r2 > 0)
{
radius = min(r1, r2);
}
else
{
radius = r1;
}
}
}
else if (attenuation[1] != 0)
{
// Solve I / attenuation.dot(1, r) < cutoff for r
//
// r * a[1] + a[0] = I / cutoff
// r = (I / cutoff - a[0]) / a[1]
float radius = (maxIntensity / cutoff - attenuation[0]) / attenuation[1];
radius = max(radius, 0.0f);
}
return Sphere(position.xyz(), radius);
}
}
CoordinateFrame GLight::frame() const
{
CoordinateFrame f;
if (rightDirection == Vector3::zero())
{
// No specified right direction; choose one automatically
if (position.w == 0)
{
// Directional light
f.lookAt(-position.xyz());
}
else
{
// Spot light
f.lookAt(spotDirection);
}
}
else
{
const Vector3& Z = -spotDirection.direction();
Vector3 X = rightDirection.direction();
// Ensure the vectors are not too close together
while (abs(X.dot(Z)) > 0.9f)
{
X = Vector3::random();
}
// Ensure perpendicular
X -= Z * Z.dot(X);
const Vector3& Y = Z.cross(X);
f.rotation.setColumn(Vector3::X_AXIS, X);
f.rotation.setColumn(Vector3::Y_AXIS, Y);
f.rotation.setColumn(Vector3::Z_AXIS, Z);
}
f.translation = position.xyz();
return f;
}
} // namespace G3D

102
engine/3d/src/GLight.hpp Normal file
View File

@@ -0,0 +1,102 @@
/**
@file GLight.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-11-12
@edited 2009-11-08
*/
#ifndef G3D_GLight_h
#define G3D_GLight_h
#include "platform.hpp"
#include "Vector4.hpp"
#include "Vector3.hpp"
#include "Color4.hpp"
namespace G3D
{
/**
A light representation that closely follows the OpenGL light format.
*/
class GLight
{
public:
/** World space position (for a directional light, w = 0 */
Vector4 position;
/** For a spot or directional light, this is the "right vector" that will be used when constructing
a reference frame(). */
Vector3 rightDirection;
/** Direction in which the light faces, if a spot light. This is the "look vector" of the light source. */
Vector3 spotDirection;
/** In <B>degrees</B>. 180 = no cutoff (point/dir). Values less than 90 = spot light */
float spotCutoff;
/** If true, G3D::SuperShader will render a cone of light large
enough to encompass the entire square that bounds the cutoff
angle. This produces a square prism instead of a cone of light
when used with a G3D::ShadowMap. for an unshadowed light this
has no effect.*/
bool spotSquare;
/** Constant, linear, quadratic */
float attenuation[3];
/** May be outside the range [0, 1] */
Color3 color;
/** If false, this light is ignored */
bool enabled;
/** If false, this light does not create specular highlights
(useful when using negative lights). */
bool specular;
/** If false, this light does not create diffuse illumination
(useful when rendering a specular-only pass). */
bool diffuse;
GLight();
/** @param toLight will be normalized */
static GLight directional(const Vector3& toLight, const Color3& color, bool specular = true, bool diffuse = true);
static GLight point(const Vector3& pos, const Color3& color, float constAtt = 1, float linAtt = 0, float quadAtt = 0.5f, bool specular = true,
bool diffuse = true);
/** @param pointDirection Will be normalized. Points in the
direction that light propagates.
@param cutOffAngleDegrees Must be on the range [0, 90]. This
is the angle from the point direction to the edge of the light
cone. I.e., a value of 45 produces a light with a 90-degree
cone of view.
*/
static GLight spot(const Vector3& pos, const Vector3& pointDirection, float cutOffAngleDegrees, const Color3& color, float constAtt = 1,
float linAtt = 0, float quadAtt = 0, bool specular = true, bool diffuse = true);
/** Creates a spot light that looks at a specific point (by calling spot() ) */
static GLight spotTarget(const Vector3& pos, const Vector3& target, float cutOffAngleDegrees, const Color3& color, float constAtt = 1,
float linAtt = 0, float quadAtt = 0, bool specular = true, bool diffuse = true)
{
return spot(pos, target - pos, cutOffAngleDegrees, color, constAtt, linAtt, quadAtt, specular, diffuse);
}
/** Returns the sphere within which this light has some noticable effect. May be infinite.
@param cutoff The value at which the light intensity is considered negligible. */
class Sphere effectSphere(float cutoff = 30.0f / 255) const;
/** Computes a reference frame (e.g., for use with G3D::ShadowMap */
class CoordinateFrame frame() const;
bool operator==(const GLight& other) const;
bool operator!=(const GLight& other) const;
};
} // namespace G3D
#endif

View File

@@ -0,0 +1,15 @@
#pragma once
namespace Aya
{
enum HandleType
{
HANDLE_RESIZE = 0,
HANDLE_MOVE,
HANDLE_ROTATE,
HANDLE_VELOCITY
};
} // namespace Aya

144
engine/3d/src/HashTrait.hpp Normal file
View File

@@ -0,0 +1,144 @@
/**
@file HashTrait.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2008-10-01
@edited 2009-11-01
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_HashTrait_h
#define G3D_HashTrait_h
#include "platform.hpp"
#include "Crypto.hpp"
#include "g3dmath.hpp"
#include "uint128.hpp"
/** Must be specialized for custom types.
@see G3D::Table for specialization requirements.
*/
template<typename T>
struct HashTrait
{
};
template<typename T>
struct HashTrait<T*>
{
static size_t hashCode(const void* k)
{
return reinterpret_cast<size_t>(k);
}
};
#if 0
template <> struct HashTrait <int> {
static size_t hashCode(int k) { return static_cast<size_t>(k); }
};
#endif
template<>
struct HashTrait<G3D::int16>
{
static size_t hashCode(G3D::int16 k)
{
return static_cast<size_t>(k);
}
};
template<>
struct HashTrait<G3D::uint16>
{
static size_t hashCode(G3D::uint16 k)
{
return static_cast<size_t>(k);
}
};
// template <> struct HashTrait <int> {
// static size_t hashCode(int k) { return static_cast<size_t>(k); }
// };
template<>
struct HashTrait<G3D::int32>
{
static size_t hashCode(G3D::int32 k)
{
return static_cast<size_t>(k);
}
};
template<>
struct HashTrait<G3D::uint32>
{
static size_t hashCode(G3D::uint32 k)
{
return static_cast<size_t>(k);
}
};
#if defined(G3D_OSX) || defined(G3D_IOS) // Aya
template<>
struct HashTrait<long unsigned int>
{
static size_t hashCode(G3D::uint32 k)
{
return static_cast<size_t>(k);
}
};
#endif
template<>
struct HashTrait<G3D::int64>
{
static size_t hashCode(G3D::int64 k)
{
return static_cast<size_t>(k);
}
};
template<>
struct HashTrait<G3D::uint64>
{
static size_t hashCode(G3D::uint64 k)
{
return static_cast<size_t>(k);
}
};
template<>
struct HashTrait<std::string>
{
static size_t hashCode(const std::string& k)
{
return static_cast<size_t>(G3D::Crypto::crc32(k.c_str(), k.size()));
}
};
template<>
struct HashTrait<G3D::uint128>
{
// Use the FNV-1 hash (http://isthe.com/chongo/tech/comp/fnv/#FNV-1).
static size_t hashCode(G3D::uint128 key)
{
static const G3D::uint128 FNV_PRIME_128(1 << 24, 0x159);
static const G3D::uint128 FNV_OFFSET_128(0xCF470AAC6CB293D2ULL, 0xF52F88BF32307F8FULL);
G3D::uint128 hash = FNV_OFFSET_128;
G3D::uint128 mask(0, 0xFF);
for (int i = 0; i < 16; ++i)
{
hash *= FNV_PRIME_128;
hash ^= (mask & key);
key >>= 8;
}
G3D::uint64 foldedHash = hash.hi ^ hash.lo;
return static_cast<size_t>((foldedHash >> 32) ^ (foldedHash & 0xFFFFFFFF));
}
};
#endif

59
engine/3d/src/HitTest.cpp Normal file
View File

@@ -0,0 +1,59 @@
#include "HitTest.hpp"
#include "Base/Part.hpp"
#include "CollisionDetection.hpp"
#include "Capsule.hpp"
namespace Aya
{
bool HitTest::hitTestBox(const Part& part, Aya::RbxRay& rayInPartCoords, Vector3& hitPointInPartCoords, float gridToReal)
{
Vector3 halfRealSize = part.gridSize * gridToReal * 0.5;
return G3D::CollisionDetection::collisionLocationForMovingPointFixedAABox(
rayInPartCoords.origin(), rayInPartCoords.direction(), G3D::AABox(-halfRealSize, halfRealSize), hitPointInPartCoords);
}
bool HitTest::hitTestBall(const Part& part, Aya::RbxRay& rayInPartCoords, Vector3& hitPointInPartCoords, float gridToReal)
{
float radius = part.gridSize.x * gridToReal * 0.5f;
return (G3D::CollisionDetection::collisionTimeForMovingPointFixedSphere(
rayInPartCoords.origin(), rayInPartCoords.direction(), G3D::Sphere(Vector3::zero(), radius), hitPointInPartCoords)
!= G3D::inf());
}
// TODO: Big optimization possible here...
// TODO: Clean up hit test here - offset stuff going on
bool HitTest::hitTestCylinder(const Part& part, Aya::RbxRay& rayInPartCoords, Vector3& hitPointInPartCoords, float gridToReal)
{
float radius = part.gridSize.z * gridToReal * 0.5f;
float axis = part.gridSize.x * gridToReal * 0.5f; // TODO - Document all of this - World and Grid space
return (G3D::CollisionDetection::collisionTimeForMovingPointFixedCapsule(rayInPartCoords.origin(), rayInPartCoords.direction(),
G3D::Capsule(Vector3::zero(), Vector3(axis, 0, 0), radius), hitPointInPartCoords)
!= G3D::inf());
}
bool HitTest::hitTest(const Part& part, Aya::RbxRay& rayInPartCoords, Vector3& hitPointInPartCoords, float gridToReal)
{
switch (part.type)
{
case Aya::BLOCK_PART:
return hitTestBox(part, rayInPartCoords, hitPointInPartCoords, gridToReal);
case Aya::BALL_PART:
return hitTestBall(part, rayInPartCoords, hitPointInPartCoords, gridToReal);
case Aya::CYLINDER_PART:
return hitTestCylinder(part, rayInPartCoords, hitPointInPartCoords, gridToReal);
default:
debugAssert(0);
}
return false;
}
} // namespace Aya

24
engine/3d/src/HitTest.hpp Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include "Utility/G3DCore.hpp"
namespace Aya
{
class Part;
class HitTest
{
private:
// hitTests
static bool hitTestBox(const Part& part, RbxRay& rayInPartCoords, Vector3& hitPointInPartCoords, float gridToReal);
static bool hitTestBall(const Part& part, RbxRay& rayInPartCoords, Vector3& hitPointInPartCoords, float gridToReal);
static bool hitTestCylinder(const Part& part, RbxRay& rayInPartCoords, Vector3& hitPointInPartCoords, float gridToReal);
public:
static bool hitTest(const Part& part, RbxRay& rayInPartCoords, Vector3& hitPointInPartCoords, float gridToReal);
};
} // namespace Aya

80
engine/3d/src/Image1.hpp Normal file
View File

@@ -0,0 +1,80 @@
/**
@file Image1.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2007-01-31
@edited 2007-01-31
*/
#ifndef G3D_IMAGE1_H
#define G3D_IMAGE1_H
#include "platform.hpp"
#include "Map2D.hpp"
#include "Color1.hpp"
#include "GImage.hpp"
namespace G3D
{
typedef ReferenceCountedPointer<class Image1> Image1Ref;
/**
Luminance image with 32-bit floating point storage.
See also G3D::Image1uint8, G3D::GImage.
*/
class Image1 : public Map2D<Color1, Color1>
{
public:
typedef Image1 Type;
typedef ReferenceCountedPointer<class Image1> Ref;
typedef Color1 Storage;
typedef Color1 Compute;
protected:
Image1(int w, int h, WrapMode wrap);
void copyGImage(const class GImage& im);
void copyArray(const Color1* src, int w, int h);
void copyArray(const Color3* src, int w, int h);
void copyArray(const Color4* src, int w, int h);
void copyArray(const Color1uint8* src, int w, int h);
void copyArray(const Color3uint8* src, int w, int h);
void copyArray(const Color4uint8* src, int w, int h);
public:
const class ImageFormat* format() const;
/** Creates an all-zero width x height image. */
static Ref createEmpty(int width, int height, WrapMode wrap = WrapMode::ERROR);
/** Creates a 0 x 0 image. */
static Ref createEmpty(WrapMode wrap = WrapMode::ERROR);
static Ref fromFile(const std::string& filename, WrapMode wrap = WrapMode::ERROR, GImage::Format fmt = GImage::AUTODETECT);
static Ref fromArray(const class Color1uint8* ptr, int width, int height, WrapMode wrap = WrapMode::ERROR);
static Ref fromArray(const class Color3uint8* ptr, int width, int height, WrapMode wrap = WrapMode::ERROR);
static Ref fromArray(const class Color4uint8* ptr, int width, int height, WrapMode wrap = WrapMode::ERROR);
static Ref fromArray(const class Color1* ptr, int width, int height, WrapMode wrap = WrapMode::ERROR);
static Ref fromArray(const class Color3* ptr, int width, int height, WrapMode wrap = WrapMode::ERROR);
static Ref fromArray(const class Color4* ptr, int width, int height, WrapMode wrap = WrapMode::ERROR);
static Ref fromImage1uint8(const ReferenceCountedPointer<class Image1uint8>& im);
static Ref fromGImage(const class GImage& im, WrapMode wrap = WrapMode::ERROR);
/** Loads from any of the file formats supported by G3D::GImage. If there is an alpha channel on the input,
it is stripped. */
void load(const std::string& filename, GImage::Format fmt = GImage::AUTODETECT);
/** Saves in any of the formats supported by G3D::GImage. */
void save(const std::string& filename, GImage::Format fmt = GImage::AUTODETECT);
};
} // namespace G3D
#endif

View File

@@ -0,0 +1,207 @@
/*
@file LightingParameters.cpp
@maintainer Morgan McGuire, matrix@graphics3d.com
@created 2002-10-05
@edited 2006-06-28
*/
#include "LightingParameters.hpp"
#include "Matrix3.hpp"
#include "spline.hpp"
#include "GLight.hpp"
#include <sys/timeb.h>
#include <sys/types.h>
#ifndef _MSC_VER
#define _timeb timeb
#define _ftime ftime
#endif
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4305)
#endif
namespace G3D
{
static const double sunRiseAndSetTime = HOUR / 2;
static const double solarYear = 365.2564 * DAY;
static const double halfSolarYear = 182.6282;
static const double moonPhaseInterval = DAY * 29.53;
// Tilt amount from the ecliptic
static const double earthTilt = toRadians(23.5);
static const double moonTilt = toRadians(5);
// (very rough) Initial star offset on Jan 1 1970 midnight
static const double initialStarRot = 1;
// Initial moon phase on Jan 1 1970 midnight
static const double initialMoonPhase = 0.75;
LightingParameters::LightingParameters()
{
physicallyCorrect = true;
setLatitude(BROWN_UNIVERSITY_LATITUDE);
setTime(0);
}
LightingParameters::LightingParameters(const GameTime _time, bool _physicallyCorrect, float _latitude)
{
physicallyCorrect = _physicallyCorrect;
setLatitude(_latitude);
setTime(_time);
}
void LightingParameters::setLatitude(float _latitude)
{
geoLatitude = _latitude;
}
void LightingParameters::setTime(const GameTime _time)
{
// wrap to a 1 day interval
double time = _time - floor(_time / DAY) * DAY;
// Calculate starfield coordinate frame
double starRot = initialStarRot - (2 * G3D::pi() * (_time - (_time * floor(_time / SIDEREAL_DAY))) / SIDEREAL_DAY);
float aX, aY, aZ;
starVec.x = cos(starRot);
starVec.y = 0;
starVec.z = sin(starRot);
starFrame.lookAt(starVec, Vector3::unitY());
trueStarFrame.lookAt(starVec, Vector3::unitY());
trueStarFrame.rotation.toEulerAnglesXYZ(aX, aY, aZ);
aX -= geoLatitude;
trueStarFrame.rotation = Matrix3::fromEulerAnglesXYZ(aX, aY, aZ);
// sunAngle = 0 at midnight
float sourceAngle = 2 * (float)G3D::pi() * time / DAY;
// Calculate fake solar and lunar positions
sunPosition.x = sin(sourceAngle);
sunPosition.y = -cos(sourceAngle);
sunPosition.z = 0;
moonPosition.x = sin(sourceAngle + (float)G3D::pi());
moonPosition.y = -cos(sourceAngle + (float)G3D::pi());
moonPosition.z = 0;
// Calculate "true" solar and lunar positions
// These positions will always be somewhat wrong
// unless _time is equal to real world GMT time,
// and the current longitude is equal to zero. Also,
// I'm assuming that the equinox-solstice interval
// occurs exactly every 90 days, which isn't exactly
// correct.
// In addition, the precession of the moon's orbit is
// not taken into account, but this should only account
// for a 5 degree margin of error at most.
float dayOfYearOffset = (_time - (_time * floor(_time / solarYear))) / DAY;
moonPhase = floor(_time / moonPhaseInterval) + initialMoonPhase;
float latRad = toRadians(geoLatitude);
float sunOffset = -earthTilt * cos(G3D::pi() * (dayOfYearOffset - halfSolarYear) / halfSolarYear) - latRad;
float moonOffset = ((-earthTilt + moonTilt) * sin(moonPhase * 4)) - latRad;
float curMoonPhase = (moonPhase * G3D::pi() * 2);
Matrix3 rotMat = Matrix3::fromAxisAngle(Vector3::unitZ().cross(sunPosition), sunOffset);
trueSunPosition = rotMat * sunPosition;
Vector3 trueMoon = Vector3(sin(curMoonPhase + sourceAngle), -cos(curMoonPhase + sourceAngle), 0);
rotMat = Matrix3::fromAxisAngleFast(Vector3::unitZ().cross(trueMoon), moonOffset);
trueMoonPosition = rotMat * trueMoon;
// Determine which light source we observe.
if (!physicallyCorrect)
{
if ((sourceAngle < (G3D::pi() / 2)) || (sourceAngle > (3 * G3D::pi() / 2)))
{
source = MOON;
sourceAngle += (float)G3D::pi();
}
else
{
source = SUN;
}
lightDirection.x = sin(sourceAngle);
lightDirection.y = -cos(sourceAngle);
lightDirection.z = 0;
}
else if (trueSunPosition.y > -.3f)
{
// The sun is always the stronger light source. When using
// physically correct parameters, the sun and moon will
// occasionally be in the visible sky at the same time.
source = SUN;
lightDirection = trueSunPosition;
}
else
{
source = MOON;
lightDirection = trueMoonPosition;
}
const Color3 dayAmbient = Color3::white() * .40f;
const Color3 dayDiffuse = Color3::white() * .75f;
{
static const double times[] = {MIDNIGHT, SUNRISE - HOUR, SUNRISE, SUNRISE + sunRiseAndSetTime / 4, SUNRISE + sunRiseAndSetTime,
SUNSET - sunRiseAndSetTime, SUNSET - sunRiseAndSetTime / 2, SUNSET, SUNSET + HOUR / 2, DAY};
static const Color3 color[] = {Color3(.2f, .2f, .2f), Color3(.1f, .1, .1), Color3(0, 0, 0), Color3(.6, .6, 0), dayDiffuse, dayDiffuse,
Color3(.1, .1, .075), Color3(.1, .05, .05), Color3(.1, .1, .1), Color3(.2, .2, .2)};
lightColor = linearSpline(time, times, color, 10);
}
{
static const double times[] = {MIDNIGHT, SUNRISE - HOUR, SUNRISE, SUNRISE + sunRiseAndSetTime / 4, SUNRISE + sunRiseAndSetTime,
SUNSET - sunRiseAndSetTime, SUNSET - sunRiseAndSetTime / 2, SUNSET, SUNSET + HOUR / 2, DAY};
static const Color3 color[] = {Color3(0, .1, .3), Color3(0, .0, .1), Color3(0, 0, 0), Color3(0, 0, 0), dayAmbient, dayAmbient,
Color3(.5, .2, .2), Color3(.05, .05, .1), Color3(0, .0, .1), Color3(0, .1, .3)};
ambient = linearSpline(time, times, color, 10);
}
{
static const double times[] = {MIDNIGHT, SUNRISE - HOUR, SUNRISE, SUNRISE + sunRiseAndSetTime / 2, SUNRISE + sunRiseAndSetTime,
SUNSET - sunRiseAndSetTime, SUNSET - sunRiseAndSetTime / 2, SUNSET, SUNSET + HOUR / 2, DAY};
static const Color3 color[] = {Color3(.1, .1, .17), Color3(.05, .06, .07), Color3(.08, .08, .01), Color3(1, 1, 1) * .75,
Color3(1, 1, 1) * .75, Color3(1, 1, 1) * .35, Color3(.5, .2, .2), Color3(.05, .05, .1), Color3(.06, .06, .07), Color3(.1, .1, .17)};
diffuseAmbient = linearSpline(time, times, color, 10);
}
{
static const double times[] = {MIDNIGHT, SUNRISE - 2 * HOUR, SUNRISE - HOUR, SUNRISE - HOUR / 2, SUNRISE, SUNRISE + sunRiseAndSetTime,
SUNSET - sunRiseAndSetTime, SUNSET, SUNSET + HOUR / 3, DAY};
static const Color3 color[] = {Color3(0, 0, 0), Color3(0, 0, 0), Color3(.07, .07, .1), Color3(.2, .15, .01), Color3(.2, .15, .01),
Color3(1, 1, 1), Color3(1, 1, 1), Color3(.4, .2, .05), Color3(0, 0, 0), Color3(0, 0, 0)};
skyAmbient = linearSpline(time, times, color, sizeof(times) / sizeof(times[0]));
}
{
static const double times[] = {MIDNIGHT, SUNRISE - 3 * HOUR, SUNRISE - 2 * HOUR, SUNRISE - HOUR / 2, SUNRISE, SUNRISE + sunRiseAndSetTime,
SUNSET - sunRiseAndSetTime, SUNSET, SUNSET + HOUR / 3, SUNSET + 2 * HOUR, SUNSET + 3 * HOUR, DAY};
static const Color3 color[] = {Color3(0.0, 0.0, 0.0), 0.7f * Color3(0.0, 0.0, 0.0), 0.7f * Color3(0.3, 0.3, 0.4), Color3(0.4, 0.3, 0.3),
Color3(0.3, 0.2, 0.3), Color3(1, 1, 1), Color3(1, 1, 1), Color3(.4, .3, .2), Color3(0.3, 0.2, 0.3), Color3(0.3, 0.2, 0.3),
Color3(0, 0, 0), Color3(0, 0, 0)};
skyAmbient2 = linearSpline(time, times, color, sizeof(times) / sizeof(times[0]));
}
emissiveScale = Color3::white();
}
GLight LightingParameters::directionalLight() const
{
return GLight::directional(lightDirection, lightColor);
}
} // namespace G3D
#ifdef _MSC_VER
#pragma warning(pop)
#endif

View File

@@ -0,0 +1,158 @@
/**
@file LightingParameters.h
@maintainer Morgan McGuire, matrix@graphics3d.com
@created 2002-10-05
@edited 2005-06-01
Copyright 2000-2005, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_LIGHTINGPARAMETERS_H
#define G3D_LIGHTINGPARAMETERS_H
#include "platform.hpp"
#include "Color3.hpp"
#include "Vector3.hpp"
#include "CoordinateFrame.hpp"
#include "GLight.hpp"
namespace G3D
{
#define BROWN_UNIVERSITY_LATITUDE 41.7333f
#define BROWN_UNIVERSITY_LONGITUDE 71.4333f
/* Initital star offset on Jan 1 1970 midnight */
/* Definition of a sidereal day */
#define SIDEREAL_DAY ((23 * HOUR) + (56 * MINUTE) + (4.071f * SECOND))
/**
Provides a reasonable (but not 100% physically correct) set of lighting
parameters based on the time of day. See also G3D::Lighting, which describes
a rich lighting environment.
*/
class LightingParameters
{
public:
/** Multiply this by all emissive values when rendering.
Some algorithms (e.g. contrib/ArticulatedModel/ToneMap) scale
down light intensity to preserve dynamic range.*/
Color3 emissiveScale;
/** Modulate sky box color */
Color3 skyAmbient;
/**
Use this for objects that do not receive directional lighting
(e.g. billboards).
*/
Color3 diffuseAmbient;
/**
Directional light color.
*/
Color3 lightColor;
Color3 ambient;
/** Only one light source, the sun or moon, is active at a given time. */
Vector3 lightDirection;
enum
{
SUN,
MOON
} source;
/** Using physically correct parameters. When false, the sun and moon
travel in a perfectly east-west arc where +x = east and -x = west. */
bool physicallyCorrect;
/** The vector <B>to</B> the sun */
Vector3 trueSunPosition;
Vector3 sunPosition;
/** The vector <B>to</B> the moon */
Vector3 trueMoonPosition;
Vector3 moonPosition;
double moonPhase;
/** The coordinate frame and vector related to the starfield */
CoordinateFrame starFrame;
CoordinateFrame trueStarFrame;
Vector3 starVec;
/* Geographic position */
float geoLatitude;
// float geoLongitude;
Color3 skyAmbient2; // used for some cheap sky gradient (secondary, horizon-level color) -- Max
LightingParameters();
/**
Sets light parameters for the sun/moon based on the
specified time since midnight, as well as geographic
latitude for starfield orientation (positive for north
of the equator and negative for south) and geographic
longitude for sun positioning (postive for east of
Greenwich, and negative for west). The latitude and
longitude is set by default to that of Providence, RI,
USA.
*/
LightingParameters(const GameTime _time, bool _physicallyCorrect = true, float _latitude = BROWN_UNIVERSITY_LATITUDE);
void setTime(const GameTime _time);
void setLatitude(float _latitude);
/**
Returns a directional light composed from the light direction
and color.
*/
GLight directionalLight() const;
};
/** A rich environment lighting model that contains both global and local sources.
See also LightingParameters, a class that describes a sun and moon lighting
model. */
class Lighting
{
private:
Lighting()
: emissiveScale(Color3::white())
{
}
public:
/** Multiply this by all emissive values when rendering.
Some algorithms (e.g. contrib/ArticulatedModel/ToneMap) scale
down light intensity to preserve dynamic range.*/
Color3 emissiveScale;
/** Light reflected from the sky (usually slightly blue) */
Color3 ambientTop;
/** Light reflected from the ground. A simpler code path is taken
if identical to ambientTop. */
Color3 ambientBottom;
/** Color to modulate environment map by */
Color3 environmentMapColor;
/** Local illumination sources that do not cast shadows. */
Array<GLight> lightArray;
/** Local illumination sources that cast shadows. */
Array<GLight> shadowedLightArray;
/** Creates a (dark) environment. */
static Lighting* create()
{
return new Lighting();
}
};
} // namespace G3D
#endif

77
engine/3d/src/Line.cpp Normal file
View File

@@ -0,0 +1,77 @@
/**
@file Line.cpp
Line class
@maintainer Morgan McGuire, graphics3d.com
@created 2001-06-02
@edited 2006-01-28
*/
#include "Line.hpp"
#include "Plane.hpp"
namespace G3D
{
Vector3 Line::intersection(const Plane& plane) const
{
float d;
Vector3 normal = plane.normal();
plane.getEquation(normal, d);
float rate = _direction.dot(normal);
if (rate == 0)
{
return Vector3::inf();
}
else
{
float t = -(d + _point.dot(normal)) / rate;
return _point + _direction * t;
}
}
Vector3 Line::closestPoint(const Vector3& pt) const
{
float t = _direction.dot(pt - _point);
return _point + _direction * t;
}
Vector3 Line::point() const
{
return _point;
}
Vector3 Line::direction() const
{
return _direction;
}
Vector3 Line::closestPoint(const Line& B, float& minDist) const
{
const Vector3& P1 = _point;
const Vector3& U1 = _direction;
Vector3 P2 = B.point();
Vector3 U2 = B.direction();
const Vector3& P21 = P2 - P1;
const Vector3& M = U2.cross(U1);
float m2 = M.length();
Vector3 R = P21.cross(M) / m2;
float t1 = R.dot(U2);
minDist = abs(P21.dot(M)) / sqrt(m2);
return P1 + t1 * U1;
}
} // namespace G3D

155
engine/3d/src/Line.hpp Normal file
View File

@@ -0,0 +1,155 @@
/**
@file Line.h
Line class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2001-06-02
@edited 2006-02-28
*/
#ifndef G3D_LINE_H
#define G3D_LINE_H
#include "platform.hpp"
#include "Vector3.hpp"
namespace G3D
{
class Plane;
/**
An infinite 3D line.
*/
class Line
{
protected:
Vector3 _point;
Vector3 _direction;
Line(const Vector3& point, const Vector3& direction)
{
_point = point;
_direction = direction.direction();
}
public:
/** Undefined (provided for creating Array<Line> only) */
inline Line() {}
virtual ~Line() {}
/**
Constructs a line from two (not equal) points.
*/
static Line fromTwoPoints(const Vector3& point1, const Vector3& point2)
{
// Aya
// return Line(point1, point2 - point1);
return Line(point1, (point2 - point1).direction());
// =====
}
/**
Creates a line from a point and a (nonzero) direction.
*/
static Line fromPointAndDirection(const Vector3& point, const Vector3& direction)
{
// Aya
// return Line(point, direction);
return Line(point, direction.direction());
// =====
}
// Aya
static Line fromPointAndUnitDirection(const Vector3& point, const Vector3& unitDirection)
{
return Line(point, unitDirection);
}
//=====
/**
Returns the closest point on the line to point.
*/
Vector3 closestPoint(const Vector3& pt) const;
/**
Returns the distance between point and the line
*/
double distance(const Vector3& point) const
{
return (closestPoint(point) - point).magnitude();
}
/** Returns a point on the line */
Vector3 point() const;
/** Returns the direction (or negative direction) of the line */
Vector3 direction() const;
/**
Returns the point where the line and plane intersect. If there
is no intersection, returns a point at infinity.
*/
Vector3 intersection(const Plane& plane) const;
/** Finds the closest point to the two lines.
@param minDist Returns the minimum distance between the lines.
@cite http://objectmix.com/graphics/133793-coordinates-closest-points-pair-skew-lines.html
*/
Vector3 closestPoint(const Line& B, float& minDist) const;
inline Vector3 closestPoint(const Line& B) const
{
float m;
return closestPoint(B, m);
}
// Aya
bool static parallel(const Line& line0, const Line& line1, float epsilon)
{
Vector3 crossAxis = line0.direction().cross(line1.direction());
return (crossAxis.squaredMagnitude() < (epsilon * epsilon));
}
// Returns false if unable to find closest points (i.e. lines are parallel)
static bool closestPoints(const Line& line0, const Line& line1, Vector3& p0, Vector3& p1)
{
const Vector3& pa = line0.point();
const Vector3& pb = line1.point();
const Vector3& ua = line0.direction();
const Vector3& ub = line1.direction();
Vector3 p = pb - pa;
float uaub = ua.dot(ub);
float q1 = ua.dot(p);
float q2 = -ub.dot(p);
float d = 1 - uaub * uaub;
if (d > 1e-6f)
{
d = 1.0f / d;
float distance0 = (q1 + uaub * q2) * d;
float distance1 = (uaub * q1 + q2) * d;
p0 = pa + distance0 * ua;
p1 = pb + distance1 * ub;
return true;
}
else
{
p0 = pa;
p1 = pb;
return false;
}
}
// =============================
};
}; // namespace G3D
#endif

View File

@@ -0,0 +1,245 @@
/**
@file LineSegment.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-02-08
@edited 2008-02-02
*/
#include "platform.hpp"
#include "LineSegment.hpp"
#include "Sphere.hpp"
#include "G3DDebug.hpp"
namespace G3D
{
Vector3 LineSegment::closestPoint(const Vector3& p) const
{
// The vector from the end of the capsule to the point in question.
Vector3 v(p - _point);
// Projection of v onto the line segment scaled by
// the length of direction.
float t = direction.dot(v);
// Avoid some square roots. Derivation:
// t/direction.length() <= direction.length()
// t <= direction.squaredLength()
if ((t >= 0) && (t <= direction.squaredMagnitude()))
{
// The point falls within the segment. Normalize direction,
// divide t by the length of direction.
return _point + direction * t / direction.squaredMagnitude();
}
else
{
// The point does not fall within the segment; see which end is closer.
// Distance from 0, squared
float d0Squared = v.squaredMagnitude();
// Distance from 1, squared
float d1Squared = (v - direction).squaredMagnitude();
if (d0Squared < d1Squared)
{
// Point 0 is closer
return _point;
}
else
{
// Point 1 is closer
return _point + direction;
}
}
}
Vector3 LineSegment::point(int i) const
{
switch (i)
{
case 0:
return _point;
case 1:
return _point + direction;
default:
debugAssertM(i == 0 || i == 1, "Argument to point must be 0 or 1");
return _point;
}
}
bool LineSegment::intersectsSolidSphere(const class Sphere& s) const
{
return distanceSquared(s.center) <= square(s.radius);
}
Vector3 LineSegment::randomPoint() const
{
return _point + uniformRandom(0, 1) * direction;
}
/////////////////////////////////////////////////////////////////////////////////////
LineSegment2D LineSegment2D::fromTwoPoints(const Vector2& p0, const Vector2& p1)
{
LineSegment2D s;
s.m_origin = p0;
s.m_direction = p1 - p0;
s.m_length = s.m_direction.length();
return s;
}
Vector2 LineSegment2D::point(int i) const
{
debugAssert(i == 0 || i == 1);
if (i == 0)
{
return m_origin;
}
else
{
return m_direction + m_origin;
}
}
Vector2 LineSegment2D::closestPoint(const Vector2& Q) const
{
// Two constants that appear in the result
const Vector2 k1(m_origin - Q);
const Vector2& k2 = m_direction;
if (fuzzyEq(m_length, 0))
{
// This line segment has no length
return m_origin;
}
// Time [0, 1] at which we hit the closest point travelling from p0 to p1.
// Derivation can be obtained by minimizing the expression
// ||P0 + (P1 - P0)t - Q||.
const float t = -k1.dot(k2) / (m_length * m_length);
if (t < 0)
{
// Clipped to low end point
return m_origin;
}
else if (t > 1)
{
// Clipped to high end point
return m_origin + m_direction;
}
else
{
// Subsitute into the line equation to find
// the point on the segment.
return m_origin + k2 * t;
}
}
float LineSegment2D::distance(const Vector2& p) const
{
Vector2 closest = closestPoint(p);
return (closest - p).length();
}
float LineSegment2D::length() const
{
return m_length;
}
Vector2 LineSegment2D::intersection(const LineSegment2D& other) const
{
if ((m_origin == other.m_origin) || (m_origin == other.m_origin + other.m_direction))
{
return m_origin;
}
if (m_origin + m_direction == other.m_origin)
{
return other.m_origin;
}
// Note: Now that we've checked the endpoints, all other parallel lines can now be assumed
// to not intersect (within numerical precision)
Vector2 dir1 = m_direction;
Vector2 dir2 = other.m_direction;
Vector2 origin1 = m_origin;
Vector2 origin2 = other.m_origin;
if (dir1.x == 0)
{
// Avoid an upcoming divide by zero
dir1 = dir1.yx();
dir2 = dir2.yx();
origin1 = origin1.yx();
origin2 = origin2.yx();
}
// t1 = ((other.m_origin.x - m_origin.x) + other.m_direction.x * t2) / m_direction.x
//
// ((other.m_origin.x - m_origin.x) + other.m_direction.x * t2) * m_direction.y / m_direction.x =
// (other.m_origin.y - m_origin.y) + other.m_direction.y * t2
//
// m = m_direction.y / m_direction.x
// d = other.m_origin - m_origin
//
// (d.x + other.m_direction.x * t2) * m = d.y + other.m_direction.y * t2
//
// d.x * m + other.m_direction.x * m * t2 = d.y + other.m_direction.y * t2
//
// d.x * m - d.y = (other.m_direction.y - other.m_direction.x * m) * t2
//
// (d.x * m - d.y) / (other.m_direction.y - other.m_direction.x * m) = t2
//
Vector2 d = origin2 - origin1;
float m = dir1.y / dir1.x;
float t2 = (d.x * m - d.y) / (dir2.y - dir2.x * m);
if (!isFinite(t2))
{
// Parallel lines: no intersection
return Vector2::inf();
}
if ((t2 < 0.0f) || (t2 > 1.0f))
{
// Intersection occurs past the end of the line segments
return Vector2::inf();
}
float t1 = (d.x + dir2.x * t2) / dir1.x;
if ((t1 < 0.0f) || (t1 > 1.0f))
{
// Intersection occurs past the end of the line segments
return Vector2::inf();
}
// Return the intersection point (computed from non-transposed
// variables even if we flipped above)
return m_origin + m_direction * t1;
}
} // namespace G3D

View File

@@ -0,0 +1,118 @@
/**
@file LineSegment.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-02-08
@edited 2008-02-02
*/
#ifndef G3D_LINESEGMENT_H
#define G3D_LINESEGMENT_H
#include "platform.hpp"
#include "Vector3.hpp"
namespace G3D
{
/**
An finite segment of an infinite 3D line.
*/
class LineSegment
{
protected:
Vector3 _point;
/** Not normalized */
Vector3 direction;
LineSegment(const Vector3& __point, const Vector3& _direction)
: _point(__point)
, direction(_direction)
{
}
public:
inline LineSegment()
: _point(Vector3::zero())
, direction(Vector3::zero())
{
}
virtual ~LineSegment() {}
/**
* Constructs a line from two (not equal) points.
*/
static LineSegment fromTwoPoints(const Vector3& point1, const Vector3& point2)
{
return LineSegment(point1, point2 - point1);
}
/** Call with 0 or 1 */
Vector3 point(int i) const;
inline float length() const
{
return direction.magnitude();
}
/**
* Returns the closest point on the line segment to point.
*/
Vector3 closestPoint(const Vector3& point) const;
/**
Returns the distance between point and the line
*/
double distance(const Vector3& p) const
{
return (closestPoint(p) - p).magnitude();
}
double distanceSquared(const Vector3& p) const
{
return (closestPoint(p) - p).squaredMagnitude();
}
/** Returns true if some part of this segment is inside the sphere */
bool intersectsSolidSphere(const class Sphere& s) const;
Vector3 randomPoint() const;
};
class LineSegment2D
{
private:
Vector2 m_origin;
/** Not normalized */
Vector2 m_direction;
/** Length of m_direction */
float m_length;
public:
LineSegment2D() {}
static LineSegment2D fromTwoPoints(const Vector2& p0, const Vector2& p1);
/** Returns the intersection of these segements (including
testing endpoints), or Vector2::inf() if they do not intersect. */
Vector2 intersection(const LineSegment2D& other) const;
Vector2 point(int i) const;
Vector2 closestPoint(const Vector2& Q) const;
float distance(const Vector2& p) const;
float length() const;
};
} // namespace G3D
#endif

743
engine/3d/src/Map2D.hpp Normal file
View File

@@ -0,0 +1,743 @@
/**
@file Map2D.h
More flexible support than provided by G3D::GImage.
@maintainer Morgan McGuire, morgan@cs.brown.edu
@created 2004-10-10
@edited 2009-03-24
*/
#ifndef G3D_Map2D_h
#define G3D_Map2D_h
#include "platform.hpp"
#include "g3dmath.hpp"
#include "Array.hpp"
#include "vectorMath.hpp"
#include "Vector2int16.hpp"
#include "ReferenceCount.hpp"
#include "AtomicInt32.hpp"
#include "GThread.hpp"
#include "Rect2D.hpp"
#include "WrapMode.hpp"
#include <string>
namespace G3D
{
namespace _internal
{
/** The default compute type for a type is the type itself. */
template<typename Storage>
class _GetComputeType
{
public:
typedef Storage Type;
};
} // namespace _internal
} // namespace G3D
// This weird syntax is needed to support VC6, which doesn't
// properly implement template overloading.
#define DECLARE_COMPUTE_TYPE(StorageType, ComputeType) \
namespace G3D \
{ \
namespace _internal \
{ \
template<> \
class _GetComputeType<StorageType> \
{ \
public: \
typedef ComputeType Type; \
}; \
} \
}
DECLARE_COMPUTE_TYPE(float32, float64)
DECLARE_COMPUTE_TYPE(float64, float64)
DECLARE_COMPUTE_TYPE(int8, float32)
DECLARE_COMPUTE_TYPE(int16, float32)
DECLARE_COMPUTE_TYPE(int32, float64)
DECLARE_COMPUTE_TYPE(int64, float64)
DECLARE_COMPUTE_TYPE(uint8, float32)
DECLARE_COMPUTE_TYPE(uint16, float32)
DECLARE_COMPUTE_TYPE(uint32, float64)
DECLARE_COMPUTE_TYPE(uint64, float64)
DECLARE_COMPUTE_TYPE(Vector2, Vector2)
DECLARE_COMPUTE_TYPE(Vector2int16, Vector2)
DECLARE_COMPUTE_TYPE(Vector3, Vector3)
DECLARE_COMPUTE_TYPE(Vector3int16, Vector3)
DECLARE_COMPUTE_TYPE(Vector4, Vector4)
DECLARE_COMPUTE_TYPE(Color3, Color3)
DECLARE_COMPUTE_TYPE(Color3uint8, Color3)
DECLARE_COMPUTE_TYPE(Color4, Color4)
DECLARE_COMPUTE_TYPE(Color4uint8, Color4)
#undef DECLARE_COMPUTE_TYPE
namespace G3D
{
/**
Map of values across a discrete 2D plane. Can be thought of as a generic class for 2D images,
allowing flexibility as to pixel format and convenient methods.
In fact, the "pixels" can be any values
on a grid that can be sensibly interpolated--RGB colors, scalars, 4D vectors, and so on.
Other "image" classes in G3D:
G3D::GImage - Supports file formats, fast, Color3uint8 and Color4uint8 formats. No interpolation.
G3D::Texture::Ref - Represents image on the graphics card (not directly readable on the CPU). Supports 2D, 3D, and a variety of interpolation
methods, loads file formats.
G3D::Image3 - A subclass of Map2D<Color3> that supports image loading and saving and conversion to Texture.
G3D::Image4 - A subclass of Map2D<Color4> that supports image loading and saving and conversion to Texture.
G3D::Image3uint8 - A subclass of Map2D<Color3uint8> that supports image loading and saving and conversion to Texture.
G3D::Image4uint8 - A subclass of Map2D<Color4uint8> that supports image loading and saving and conversion to Texture.
There are two type parameters-- the first (@ Storage) is the type
used to store the "pixel" values efficiently and
the second (@a Compute) is
the type operated on by computation. The Compute::Compute(Storage&) constructor
is used to convert between storage and computation types.
@a Storage is often an integer version of @a Compute, for example
<code>Map2D<double, uint8></code>. By default, the computation type is:
<pre>
Storage Computation
uint8 float32
uint16 float32
uint32 float64
uint64 float64
int8 float32
int16 float32
int32 float64
int64 float64
float32 float64
float64 float64
Vector2 Vector2
Vector2int16 Vector2
Vector3 Vector3
Vector3int16 Vector3
Vector4 Vector4
Color3 Color3
Color3uint8 Color3
Color4 Color4
Color4uint8 Color4
</pre>
Any other storage type defaults to itself as the computation type.
The computation type can be any that
supports lerp, +, -, *, /, and an empty constructor.
Assign value:
<code>im->set(x, y, 7);</code> or
<code>im->get(x, y) = 7;</code>
Read value:
<code>int c = im(x, y);</code>
Can also sample with nearest neighbor, bilinear, and bicubic
interpolation.
Sampling follows OpenGL conventions, where
pixel values represent grid points and (0.5, 0.5) is half-way
between two vertical and two horizontal grid points.
To draw an image of dimensions w x h with nearest neighbor
sampling, render pixels from [0, 0] to [w - 1, h - 1].
Under the WrapMode::CLAMP wrap mode, the value of bilinear interpolation
becomes constant outside [1, w - 2] horizontally. Nearest neighbor
interpolation is constant outside [0, w - 1] and bicubic outside
[3, w - 4]. The class does not offer quadratic interpolation because
the interpolation filter could not center over a pixel.
@author Morgan McGuire, http://graphics.cs.williams.edu
*/
template<typename Storage, typename Compute = typename G3D::_internal::_GetComputeType<Storage>::Type>
class Map2D : public ReferenceCountedObject
{
//
// It doesn't make sense to automatically convert from Compute back to Storage
// because the rounding rule (and scaling) is application dependent.
// Thus the interpolation methods all return type Compute.
//
public:
typedef Storage StorageType;
typedef Compute ComputeType;
typedef Map2D<Storage, Compute> Type;
typedef ReferenceCountedPointer<Map2D> Ref;
protected:
Storage ZERO;
/** Width, in pixels. */
uint32 w;
/** Height, in pixels. */
uint32 h;
WrapMode _wrapMode;
/** 0 if no mutating method has been invoked
since the last call to setChanged(); */
AtomicInt32 m_changed;
Array<Storage> data;
/** Handles the exceptional cases from get */
const Storage& slowGet(int x, int y, WrapMode wrap)
{
switch (wrap)
{
case WrapMode::CLAMP:
return fastGet(iClamp(x, 0, w - 1), iClamp(y, 0, h - 1));
case WrapMode::TILE:
return fastGet(iWrap(x, w), iWrap(y, h));
case WrapMode::ZERO:
return ZERO;
case WrapMode::ERROR:
alwaysAssertM(((uint32)x < w) && ((uint32)y < h), format("Index out of bounds: (%d, %d), w = %d, h = %d", x, y, w, h));
// intentionally fall through
case WrapMode::IGNORE:
// intentionally fall through
default:
{
static Storage temp;
return temp;
}
}
}
public:
/** Unsafe access to the underlying data structure with no wrapping support; requires that (x, y) is in bounds. */
inline const Storage& fastGet(int x, int y) const
{
debugAssert(((uint32)x < w) && ((uint32)y < h));
return data[x + y * w];
}
/** Unsafe access to the underlying data structure with no wrapping support; requires that (x, y) is in bounds. */
inline void fastSet(int x, int y, const Storage& v)
{
debugAssert(((uint32)x < w) && ((uint32)y < h));
data[x + y * w] = v;
}
protected:
/** Given four control points and a value on the range [0, 1)
evaluates the Catmull-rom spline between the times of the
middle two control points */
Compute bicubic(const Compute* ctrl, double s) const
{
// f = B * S * ctrl'
// B matrix: Catmull-Rom spline basis
static const double B[4][4] = {{0.0, -0.5, 1.0, -0.5}, {1.0, 0.0, -2.5, 1.5}, {0.0, 0.5, 2.0, -1.5}, {0.0, 0.0, -0.5, 0.5}};
// S: Powers of the fraction
double S[4];
double s2 = s * s;
S[0] = 1.0;
S[1] = s;
S[2] = s2;
S[3] = s2 * s;
Compute sum(ZERO);
for (int c = 0; c < 4; ++c)
{
double coeff = 0.0;
for (int power = 0; power < 4; ++power)
{
coeff += B[c][power] * S[power];
}
sum += ctrl[c] * coeff;
}
return sum;
}
Map2D(int w, int h, WrapMode wrap)
: w(0)
, h(0)
, _wrapMode(wrap)
, m_changed(1)
{
ZERO = Storage(Compute(Storage()) * 0);
resize(w, h);
}
public:
/**
Although Map2D is not threadsafe (except for the setChanged() method),
you can use this mutex to create your own threadsafe access to a Map2D.
Not used by the default implementation.
*/
GMutex mutex;
static Ref create(int w = 0, int h = 0, WrapMode wrap = WrapMode::ERROR)
{
return new Map2D(w, h, wrap);
}
/** Resizes without clearing, leaving garbage.
*/
void resize(uint32 newW, uint32 newH)
{
if ((newW != w) || (newH != h))
{
w = newW;
h = newH;
data.resize(w * h);
setChanged(true);
}
}
/**
Returns true if this map has been written to since the last call to setChanged(false).
This is useful if you are caching a texture map other value that must be recomputed
whenever this changes.
*/
bool changed()
{
return m_changed.value() != 0;
}
/** Set/unset the changed flag. */
void setChanged(bool c)
{
m_changed = c ? 1 : 0;
}
/** Returns a pointer to the underlying row-major data. There is no padding at the end of the row.
Be careful--this will be reallocated during a resize. You should call setChanged(true) if you mutate the array.*/
Storage* getCArray()
{
return data.getCArray();
}
const Storage* getCArray() const
{
return data.getCArray();
}
/** Row-major array. You should call setChanged(true) if you mutate the array. */
Array<Storage>& getArray()
{
return data;
}
const Array<Storage>& getArray() const
{
return data;
}
/** is (x, y) strictly within the image bounds, or will it trigger some kind of wrap mode */
inline bool inBounds(int x, int y) const
{
return (((uint32)x < w) && ((uint32)y < h));
}
/** is (x, y) strictly within the image bounds, or will it trigger some kind of wrap mode */
inline bool inBounds(const Vector2int16& v) const
{
return inBounds(v.x, v.y);
}
/** Get the value at (x, y).
Note that the type of image->get(x, y) is
the storage type, not the computation
type. If the constructor promoting Storage to Compute rescales values
(as, for example Color3(Color3uint8&) does), this will not match the value
returned by Map2D::nearest.
*/
inline const Storage& get(int x, int y, WrapMode wrap) const
{
if (((uint32)x < w) && ((uint32)y < h))
{
return data[x + y * w];
}
else
{
// Remove the const to allow a slowGet on this object
// (we're returning a const reference so this is ok)
return const_cast<Type*>(this)->slowGet(x, y, wrap);
}
#ifndef G3D_WIN32
// gcc gives a useless warning that the above code might reach the end of the function;
// we use this line to supress the warning.
return ZERO;
#endif
}
inline const Storage& get(int x, int y) const
{
return get(x, y, _wrapMode);
}
inline const Storage& get(const Vector2int16& p) const
{
return get(p.x, p.y, _wrapMode);
}
inline const Storage& get(const Vector2int16& p, WrapMode wrap) const
{
return get(p.x, p.y, wrap);
}
inline Storage& get(int x, int y, WrapMode wrap)
{
return const_cast<Storage&>(const_cast<const Type*>(this)->get(x, y, wrap));
#ifndef G3D_WIN32
// gcc gives a useless warning that the above code might reach the end of the function;
// we use this line to supress the warning.
return ZERO;
#endif
}
inline Storage& get(int x, int y)
{
return const_cast<Storage&>(const_cast<const Type*>(this)->get(x, y));
#ifndef G3D_WIN32
// gcc gives a useless warning that the above code might reach the end of the function;
// we use this line to supress the warning.
return ZERO;
#endif
}
inline Storage& get(const Vector2int16& p)
{
return get(p.x, p.y);
}
/** Sets the changed flag to true */
inline void set(const Vector2int16& p, const Storage& v)
{
set(p.x, p.y, v);
}
/** Sets the changed flag to true */
void set(int x, int y, const Storage& v, WrapMode wrap)
{
setChanged(true);
if (((uint32)x < w) && ((uint32)y < h))
{
// In bounds, wrapping isn't an issue.
data[x + y * w] = v;
}
else
{
const_cast<Storage&>(slowGet(x, y, wrap)) = v;
}
}
void set(int x, int y, const Storage& v)
{
set(x, y, v, _wrapMode);
}
void setAll(const Storage& v)
{
for (int i = 0; i < data.size(); ++i)
{
data[i] = v;
}
setChanged(true);
}
/** flips if @a flip is true*/
void maybeFlipVertical(bool flip)
{
if (flip)
{
flipVertical();
}
}
virtual void flipVertical()
{
int halfHeight = h / 2;
Storage* d = data.getCArray();
for (int y = 0; y < halfHeight; ++y)
{
int o1 = y * w;
int o2 = (h - y - 1) * w;
for (int x = 0; x < (int)w; ++x)
{
int i1 = o1 + x;
int i2 = o2 + x;
Storage temp = d[i1];
d[i1] = d[i2];
d[i2] = temp;
}
}
setChanged(true);
}
virtual void flipHorizontal()
{
int halfWidth = w / 2;
Storage* d = data.getCArray();
for (int x = 0; x < halfWidth; ++x)
{
for (int y = 0; y < (int)h; ++y)
{
int i1 = y * w + x;
int i2 = y * w + (w - x - 1);
Storage temp = d[i1];
d[i1] = d[i2];
d[i2] = temp;
}
}
setChanged(true);
}
/**
Crops this map so that it only contains pixels between (x, y) and (x + w - 1, y + h - 1) inclusive.
*/
virtual void crop(int newX, int newY, int newW, int newH)
{
alwaysAssertM(newX + newW <= (int)w, "Cannot grow when cropping");
alwaysAssertM(newY + newH <= (int)h, "Cannot grow when cropping");
alwaysAssertM(newX >= 0 && newY >= 0, "Origin out of bounds.");
// Always safe to copy towards the upper left, provided
// that we're iterating towards the lower right. This lets us avoid
// reallocating the underlying array.
for (int y = 0; y < newH; ++y)
{
for (int x = 0; x < newW; ++x)
{
data[x + y * newW] = data[(x + newX) + (y + newY) * w];
}
}
resize(newW, newH);
}
/** iRounds to the nearest x0 and y0. */
virtual void crop(const Rect2D& rect)
{
crop(iRound(rect.x0()), iRound(rect.y0()), iRound(rect.x1()) - iRound(rect.x0()), iRound(rect.y1()) - iRound(rect.y0()));
}
/** Returns the nearest neighbor. Pixel values are considered
to be at the upper left corner, so <code>image->nearest(x, y) == image(x, y)</code>
*/
inline Compute nearest(float x, float y, WrapMode wrap) const
{
int ix = iRound(x);
int iy = iRound(y);
return Compute(get(ix, iy, wrap));
}
inline Compute nearest(float x, float y) const
{
return nearest(x, y, _wrapMode);
}
inline Compute nearest(const Vector2& p) const
{
return nearest(p.x, p.y);
}
/** Returns the average value of all elements of the map */
Compute average() const
{
if ((w == 0) || (h == 0))
{
return ZERO;
}
// To avoid overflows, compute the average of row averages
Compute rowSum = ZERO;
for (unsigned int y = 0; y < h; ++y)
{
Compute sum = ZERO;
int offset = y * w;
for (unsigned int x = 0; x < w; ++x)
{
sum += Compute(data[offset + x]);
}
rowSum += sum * (1.0f / w);
}
return rowSum * (1.0f / h);
}
/**
Needs to access elements from (floor(x), floor(y))
to (floor(x) + 1, floor(y) + 1) and will use
the wrap mode appropriately (possibly generating
out of bounds errors).
Guaranteed to match nearest(x, y) at integers. */
Compute bilinear(float x, float y, WrapMode wrap) const
{
const int i = iFloor(x);
const int j = iFloor(y);
const float fX = x - i;
const float fY = y - j;
// Horizontal interpolation, first row
const Compute& t0 = get(i, j, wrap);
const Compute& t1 = get(i + 1, j, wrap);
// Horizontal interpolation, second row
const Compute& t2 = get(i, j + 1, wrap);
const Compute& t3 = get(i + 1, j + 1, wrap);
const Compute& A = lerp(t0, t1, fX);
const Compute& B = lerp(t2, t3, fX);
// Vertical interpolation
return lerp(A, B, fY);
}
Compute bilinear(float x, float y) const
{
return bilinear(x, y, _wrapMode);
}
inline Compute bilinear(const Vector2& p) const
{
return bilinear(p.x, p.y, _wrapMode);
}
inline Compute bilinear(const Vector2& p, WrapMode wrap) const
{
return bilinear(p.x, p.y, wrap);
}
/**
Uses Catmull-Rom splines to interpolate between grid
values. Guaranteed to match nearest(x, y) at integers.
*/
Compute bicubic(float x, float y, WrapMode wrap) const
{
int i = iFloor(x);
int j = iFloor(y);
float fX = x - i;
float fY = y - j;
Compute vsample[4];
for (int v = 0; v < 4; ++v)
{
// Horizontal interpolation
Compute hsample[4];
for (int u = 0; u < 4; ++u)
{
hsample[u] = Compute(get(i + u - 1, j + v - 1, wrap));
}
vsample[v] = bicubic(hsample, fX);
}
// Vertical interpolation
return bicubic(vsample, fY);
}
Compute bicubic(float x, float y) const
{
return bicubic(x, y, _wrapMode);
}
inline Compute bicubic(const Vector2& p, WrapMode wrap) const
{
return bicubic(p.x, p.y, wrap);
}
inline Compute bicubic(const Vector2& p) const
{
return bicubic(p.x, p.y, _wrapMode);
}
/** Pixel width */
inline int32 width() const
{
return (int32)w;
}
/** Pixel height */
inline int32 height() const
{
return (int32)h;
}
/** Dimensions in pixels */
Vector2int16 size() const
{
return Vector2int16(w, h);
}
/** Rectangle from (0, 0) to (w, h) */
Rect2D rect2DBounds() const
{
return Rect2D::xywh(0, 0, w, h);
}
/** Number of bytes occupied by the image data and this structure */
size_t sizeInMemory() const
{
return data.size() * sizeof(Storage) + sizeof(*this);
}
WrapMode wrapMode() const
{
return _wrapMode;
}
void setWrapMode(WrapMode m)
{
_wrapMode = m;
}
};
} // namespace G3D
#endif // G3D_IMAGE_H

83
engine/3d/src/Matrix2.hpp Normal file
View File

@@ -0,0 +1,83 @@
#ifndef G3D_Matrix2_h
#define G3D_Matrix2_h
#include "platform.hpp"
#include "Vector2.hpp"
namespace G3D
{
/** @beta */
class Matrix2
{
private:
float data[2][2];
public:
Matrix2()
{
data[0][0] = 1.0f;
data[0][1] = 0.0f;
data[1][0] = 0.0f;
data[1][1] = 1.0f;
}
Matrix2(float v00, float v01, float v10, float v11)
{
data[0][0] = v00;
data[0][1] = v01;
data[1][0] = v10;
data[1][1] = v11;
}
static Matrix2 identity()
{
return Matrix2(1.0f, 0.0f, 0.0f, 1.0f);
}
Vector2 operator*(const Vector2& v) const
{
return Vector2(data[0][0] * v[0] + data[0][1] * v[1], data[1][0] * v[0] + data[1][1] * v[1]);
}
Matrix2 inverse() const
{
return Matrix2(data[1][1], -data[0][1], -data[1][0], data[0][0]) * (1.0f / determinant());
}
Matrix2 transpose() const
{
return Matrix2(data[0][0], data[1][0], data[0][1], data[1][1]);
}
float determinant() const
{
return data[0][0] * data[1][1] - data[0][1] * data[1][0];
}
Matrix2 operator*(float f) const
{
return Matrix2(data[0][0] * f, data[0][1] * f, data[1][0] * f, data[1][1] * f);
}
Matrix2 operator/(float f) const
{
return Matrix2(data[0][0] / f, data[0][1] / f, data[1][0] / f, data[1][1] / f);
}
float* operator[](int i)
{
debugAssert(i >= 0 && i <= 2);
return data[i];
}
const float* operator[](int i) const
{
debugAssert(i >= 0 && i <= 1);
return data[i];
}
};
} // namespace G3D
#endif

1925
engine/3d/src/Matrix3.cpp Normal file

File diff suppressed because it is too large Load Diff

398
engine/3d/src/Matrix3.hpp Normal file
View File

@@ -0,0 +1,398 @@
/**
@file Matrix3.h
3x3 matrix class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@cite Portions based on Dave Eberly's Magic Software Library at <A HREF="http://www.magic-software.com">http://www.magic-software.com</A>
@created 2001-06-02
@edited 2006-04-05
*/
#ifndef G3D_Matrix3_h
#define G3D_Matrix3_h
#include "platform.hpp"
#include "Vector3.hpp"
#include "Vector4.hpp"
#include "debugAssert.hpp"
#include <cstring>
namespace G3D
{
#ifdef _MSC_VER
// Turn off "conditional expression is constant" warning; MSVC generates this
// for debug assertions in inlined methods.
#pragma warning(disable : 4127)
#endif
/**
3x3 matrix. Do not subclass.
*/
class Matrix3
{
private:
float elt[3][3];
// Hidden operators
bool operator<(const Matrix3&) const;
bool operator>(const Matrix3&) const;
bool operator<=(const Matrix3&) const;
bool operator>=(const Matrix3&) const;
public:
/** Initial values are undefined for performance. See also
Matrix3::zero(), Matrix3::identity(), Matrix3::fromAxisAngle, etc.*/
inline Matrix3() {}
Matrix3(const float aafEntry[3][3]);
Matrix3(float fEntry00, float fEntry01, float fEntry02, float fEntry10, float fEntry11, float fEntry12, float fEntry20, float fEntry21,
float fEntry22);
bool fuzzyEq(const Matrix3& b) const;
/** Constructs a matrix from a quaternion.
@cite Graphics Gems II, p. 351--354
@cite Implementation from Watt and Watt, pg 362*/
Matrix3(const class Quat& q);
/** Returns true if column(0).cross(column(1)).dot(column(2)) > 0. */
bool isRightHanded() const;
/**
Sets all elements.
*/
void set(float fEntry00, float fEntry01, float fEntry02, float fEntry10, float fEntry11, float fEntry12, float fEntry20, float fEntry21,
float fEntry22);
/**
* member access, allows use of construct mat[r][c]
*/
inline float* operator[](int iRow)
{
debugAssert(iRow >= 0);
debugAssert(iRow < 3);
return (float*)&elt[iRow][0];
}
inline const float* operator[](int iRow) const
{
debugAssert(iRow >= 0);
debugAssert(iRow < 3);
return (const float*)&elt[iRow][0];
}
inline operator float*()
{
return (float*)&elt[0][0];
}
inline operator const float*() const
{
return (const float*)&elt[0][0];
}
Vector3 column(int c) const;
const Vector3& row(int iRow) const
{
debugAssert((0 <= iRow) && (iRow < 3));
return *reinterpret_cast<const Vector3*>(elt[iRow]);
}
void setColumn(int iCol, const Vector3& vector);
void setRow(int iRow, const Vector3& vector);
// assignment and comparison
inline Matrix3& operator=(const Matrix3& rkMatrix)
{
memcpy(elt, rkMatrix.elt, 9 * sizeof(float));
return *this;
}
bool operator==(const Matrix3& rkMatrix) const;
bool operator!=(const Matrix3& rkMatrix) const;
// arithmetic operations
Matrix3 operator+(const Matrix3& rkMatrix) const;
Matrix3 operator-(const Matrix3& rkMatrix) const;
/** Matrix-matrix multiply */
Matrix3 operator*(const Matrix3& rkMatrix) const;
Matrix3 operator-() const;
Matrix3& operator+=(const Matrix3& rkMatrix);
Matrix3& operator-=(const Matrix3& rkMatrix);
Matrix3& operator*=(const Matrix3& rkMatrix);
static inline Matrix3 fillRotation(const Vector3& axis)
{
float angle = axis.magnitude();
if (angle <= 0.0f)
{
return Matrix3::identity();
}
Vector3 normalAxis = 1.0f / angle * axis;
return Matrix3::fromAxisAngle(normalAxis, angle);
}
/**
* matrix * vector [3x3 * 3x1 = 3x1]
*/
inline Vector3 operator*(const Vector3& v) const
{
Vector3 kProd;
for (int r = 0; r < 3; ++r)
{
kProd[r] = elt[r][0] * v[0] + elt[r][1] * v[1] + elt[r][2] * v[2];
}
return kProd;
}
/**
* vector * matrix [1x3 * 3x3 = 1x3]
*/
friend Vector3 operator*(const Vector3& rkVector, const Matrix3& rkMatrix);
/**
* matrix * scalar
*/
Matrix3 operator*(float fScalar) const;
/** scalar * matrix */
friend Matrix3 operator*(double fScalar, const Matrix3& rkMatrix);
friend Matrix3 operator*(float fScalar, const Matrix3& rkMatrix);
friend Matrix3 operator*(int fScalar, const Matrix3& rkMatrix);
Matrix3& operator*=(float k);
Matrix3& operator/=(float k);
private:
/** Multiplication where out != A and out != B */
static void _mul(const Matrix3& A, const Matrix3& B, Matrix3& out);
public:
// Aya
inline static void fastMul(const Matrix3& A, const Matrix3& B, Matrix3& out)
{
_mul(A, B, out);
}
// ======
/** Optimized implementation of out = A * B. It is safe (but slow) to call
with A, B, and out possibly pointer equal to one another.*/
// This is a static method so that it is not ambiguous whether "this"
// is an input or output argument.
inline static void mul(const Matrix3& A, const Matrix3& B, Matrix3& out)
{
if ((&out == &A) || (&out == &B))
{
// We need a temporary anyway, so revert to the stack method.
out = A * B;
}
else
{
// Optimized in-place multiplication.
_mul(A, B, out);
}
}
private:
static void _transpose(const Matrix3& A, Matrix3& out);
public:
/** Optimized implementation of out = A.transpose(). It is safe (but slow) to call
with A and out possibly pointer equal to one another.
Note that <CODE>A.transpose() * v</CODE> can be computed
more efficiently as <CODE>v * A</CODE>.
*/
inline static void transpose(const Matrix3& A, Matrix3& out)
{
if (&A == &out)
{
out = A.transpose();
}
else
{
_transpose(A, out);
}
}
/** Returns true if the rows and column L2 norms are 1.0 and the rows are orthogonal. */
bool isOrthonormal() const;
Matrix3 transpose() const;
bool inverse(Matrix3& rkInverse, float fTolerance = 1e-06) const;
Matrix3 inverse(float fTolerance = 1e-06) const;
float determinant() const;
/** singular value decomposition */
void singularValueDecomposition(Matrix3& rkL, Vector3& rkS, Matrix3& rkR) const;
/** singular value decomposition */
void singularValueComposition(const Matrix3& rkL, const Vector3& rkS, const Matrix3& rkR);
/** Gram-Schmidt orthonormalization (applied to columns of rotation matrix) */
void orthonormalize();
/** orthogonal Q, diagonal D, upper triangular U stored as (u01,u02,u12) */
void qDUDecomposition(Matrix3& rkQ, Vector3& rkD, Vector3& rkU) const;
/**
Polar decomposition of a matrix. Based on pseudocode from Nicholas J
Higham, "Computing the Polar Decomposition -- with Applications Siam
Journal of Science and Statistical Computing, Vol 7, No. 4, October
1986.
Decomposes A into R*S, where R is orthogonal and S is symmetric.
Ken Shoemake's "Matrix animation and polar decomposition"
in Proceedings of the conference on Graphics interface '92
seems to be better known in the world of graphics, but Higham's version
uses a scaling constant that can lead to faster convergence than
Shoemake's when the initial matrix is far from orthogonal.
*/
void polarDecomposition(Matrix3& R, Matrix3& S) const;
/**
* Matrix norms.
*/
float spectralNorm() const;
float squaredFrobeniusNorm() const;
float frobeniusNorm() const;
float l1Norm() const;
float lInfNorm() const;
float diffOneNorm(const Matrix3& y) const;
/** matrix must be orthonormal */
void toAxisAngle(Vector3& rkAxis, float& rfRadians) const;
static Matrix3 fromDiagonal(const Vector3& d)
{
return Matrix3(d.x, 0, 0, 0, d.y, 0, 0, 0, d.z);
}
static Matrix3 fromAxisAngleFast(const Vector3& _normalizedAxis, float fRadians);
// Aya
static Matrix3 fromAxisAngle(const Vector3& _axis, float fRadians)
{
return fromAxisAngleFast(_axis.direction(), fRadians);
}
// ======
/**
* The matrix must be orthonormal. The decomposition is yaw*pitch*roll
* where yaw is rotation about the Up vector, pitch is rotation about the
* right axis, and roll is rotation about the Direction axis.
*/
bool toEulerAnglesXYZ(float& rfYAngle, float& rfPAngle, float& rfRAngle) const;
bool toEulerAnglesXZY(float& rfYAngle, float& rfPAngle, float& rfRAngle) const;
bool toEulerAnglesYXZ(float& rfYAngle, float& rfPAngle, float& rfRAngle) const;
bool toEulerAnglesYZX(float& rfYAngle, float& rfPAngle, float& rfRAngle) const;
bool toEulerAnglesZXY(float& rfYAngle, float& rfPAngle, float& rfRAngle) const;
bool toEulerAnglesZYX(float& rfYAngle, float& rfPAngle, float& rfRAngle) const;
static Matrix3 fromEulerAnglesXYZ(float fYAngle, float fPAngle, float fRAngle);
static Matrix3 fromEulerAnglesXZY(float fYAngle, float fPAngle, float fRAngle);
static Matrix3 fromEulerAnglesYXZ(float fYAngle, float fPAngle, float fRAngle);
static Matrix3 fromEulerAnglesYZX(float fYAngle, float fPAngle, float fRAngle);
static Matrix3 fromEulerAnglesZXY(float fYAngle, float fPAngle, float fRAngle);
static Matrix3 fromEulerAnglesZYX(float fYAngle, float fPAngle, float fRAngle);
/** eigensolver, matrix must be symmetric */
void eigenSolveSymmetric(float afEigenvalue[3], Vector3 akEigenvector[3]) const;
static void tensorProduct(const Vector3& rkU, const Vector3& rkV, Matrix3& rkProduct);
std::string toString() const;
static const float EPSILON;
// Aya NOTE:
// The folowing G3D comment is wrong. Arseny prove this by compiling code
// and checking the pointers are the same, also by reading assembly. I keep the comment here
// for the future generations.
// *** Original G3D comment ***
// Special values.
// The unguaranteed order of initialization of static variables across
// translation units can be a source of annoying bugs, so now the static
// special values (like Vector3::ZERO, Color3::WHITE, ...) are wrapped
// inside static functions that return references to them.
// These functions are intentionally not inlined, because:
// "You might be tempted to write [...] them as inline functions
// inside their respective header files, but this is something you
// must definitely not do. An inline function can be duplicated
// in every file in which it appears <20><><EFBFBD><EFBFBD> and this duplication
// includes the static object definition. Because inline functions
// automatically default to internal linkage, this would result in
// having multiple static objects across the various translation
// units, which would certainly cause problems. So you must
// ensure that there is only one definition of each wrapping
// function, and this means not making the wrapping functions inline",
// according to Chapter 10 of "Thinking in C++, 2nd ed. Volume 1" by Bruce Eckel,
// http://www.mindview.net/
// *** End of G3D comment ***
static const Matrix3& zero()
{
static Matrix3 m(0, 0, 0, 0, 0, 0, 0, 0, 0);
return m;
}
static const Matrix3& identity()
{
static Matrix3 m(1, 0, 0, 0, 1, 0, 0, 0, 1);
return m;
}
protected:
// support for eigensolver
void tridiagonal(float afDiag[3], float afSubDiag[3]);
bool qLAlgorithm(float afDiag[3], float afSubDiag[3]);
// support for singular value decomposition
static const float ms_fSvdEpsilon;
static const int ms_iSvdMaxIterations;
static void bidiagonalize(Matrix3& kA, Matrix3& kL, Matrix3& kR);
static void golubKahanStep(Matrix3& kA, Matrix3& kL, Matrix3& kR);
// support for spectral norm
static float maxCubicRoot(float afCoeff[3]);
};
//----------------------------------------------------------------------------
/** <code>v * M == M.transpose() * v</code> */
inline Vector3 operator*(const Vector3& rkPoint, const Matrix3& rkMatrix)
{
Vector3 kProd;
for (int r = 0; r < 3; ++r)
{
kProd[r] = rkPoint[0] * rkMatrix.elt[0][r] + rkPoint[1] * rkMatrix.elt[1][r] + rkPoint[2] * rkMatrix.elt[2][r];
}
return kProd;
}
} // namespace G3D
#endif

480
engine/3d/src/Matrix4.cpp Normal file
View File

@@ -0,0 +1,480 @@
/**
@file Matrix4.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-10-02
@edited 2010-01-29
*/
#include "platform.hpp"
#include "Matrix4.hpp"
#include "Matrix3.hpp"
#include "Vector4.hpp"
#include "Vector3.hpp"
#include "CoordinateFrame.hpp"
#include "Rect2D.hpp"
namespace G3D
{
const Matrix4& Matrix4::identity()
{
static Matrix4 m(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
return m;
}
const Matrix4& Matrix4::zero()
{
static Matrix4 m(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
return m;
}
Matrix4::Matrix4(const class CoordinateFrame& cframe)
{
for (int r = 0; r < 3; ++r)
{
for (int c = 0; c < 3; ++c)
{
elt[r][c] = cframe.rotation[r][c];
}
elt[r][3] = cframe.translation[r];
}
elt[3][0] = 0.0f;
elt[3][1] = 0.0f;
elt[3][2] = 0.0f;
elt[3][3] = 1.0f;
}
Matrix4::Matrix4(const Matrix3& upper3x3, const Vector3& lastCol)
{
for (int r = 0; r < 3; ++r)
{
for (int c = 0; c < 3; ++c)
{
elt[r][c] = upper3x3[r][c];
}
elt[r][3] = lastCol[r];
}
elt[3][0] = 0.0f;
elt[3][1] = 0.0f;
elt[3][2] = 0.0f;
elt[3][3] = 1.0f;
}
Matrix3 Matrix4::upper3x3() const
{
return Matrix3(elt[0][0], elt[0][1], elt[0][2], elt[1][0], elt[1][1], elt[1][2], elt[2][0], elt[2][1], elt[2][2]);
}
Matrix4 Matrix4::orthogonalProjection(const class Rect2D& rect, float nearval, float farval, float upDirection)
{
return Matrix4::orthogonalProjection(rect.x0(), rect.x1(), rect.y1(), rect.y0(), nearval, farval, upDirection);
}
Matrix4 Matrix4::orthogonalProjection(float left, float right, float bottom, float top, float nearval, float farval, float upDirection)
{
// Adapted from Mesa. Note that Microsoft (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/opengl/glfunc03_8qnj.asp)
// and Linux (http://www.xfree86.org/current/glOrtho.3.html) have different matrices shown in their documentation.
float x, y, z;
float tx, ty, tz;
x = 2.0f / (right - left);
y = 2.0f / (top - bottom);
z = -2.0f / (farval - nearval);
tx = -(right + left) / (right - left);
ty = -(top + bottom) / (top - bottom);
tz = -(farval + nearval) / (farval - nearval);
y *= upDirection;
ty *= upDirection;
return Matrix4(x, 0.0f, 0.0f, tx, 0.0f, y, 0.0f, ty, 0.0f, 0.0f, z, tz, 0.0f, 0.0f, 0.0f, 1.0f);
}
Matrix4 Matrix4::perspectiveProjection(float left, float right, float bottom, float top, float nearval, float farval, float upDirection)
{
float x, y, a, b, c, d;
x = (2.0f * nearval) / (right - left);
y = (2.0f * nearval) / (top - bottom);
a = (right + left) / (right - left);
b = (top + bottom) / (top - bottom);
if (farval >= finf())
{
// Infinite view frustum
c = -1.0f;
d = -2.0f * nearval;
}
else
{
c = -(farval + nearval) / (farval - nearval);
d = -(2.0f * farval * nearval) / (farval - nearval);
}
debugAssertM(abs(upDirection) == 1.0f, "upDirection must be -1 or +1");
y *= upDirection;
b *= upDirection;
return Matrix4(x, 0, a, 0, 0, y, b, 0, 0, 0, c, d, 0, 0, -1, 0);
}
void Matrix4::getPerspectiveProjectionParameters(
float& left, float& right, float& bottom, float& top, float& nearval, float& farval, float upDirection) const
{
debugAssertM(abs(upDirection) == 1.0f, "upDirection must be -1 or +1");
float x = elt[0][0];
float y = elt[1][1] * upDirection;
float a = elt[0][2];
float b = elt[1][2] * upDirection;
float c = elt[2][2];
float d = elt[2][3];
// Verify that this really is a projection matrix
debugAssertM(elt[3][2] == -1, "Not a projection matrix");
debugAssertM(elt[0][1] == 0, "Not a projection matrix");
debugAssertM(elt[0][3] == 0, "Not a projection matrix");
debugAssertM(elt[1][3] == 0, "Not a projection matrix");
debugAssertM(elt[3][3] == 0, "Not a projection matrix");
debugAssertM(elt[1][0] == 0, "Not a projection matrix");
debugAssertM(elt[2][0] == 0, "Not a projection matrix");
debugAssertM(elt[2][1] == 0, "Not a projection matrix");
debugAssertM(elt[3][0] == 0, "Not a projection matrix");
debugAssertM(elt[3][1] == 0, "Not a projection matrix");
if (c == -1)
{
farval = finf();
nearval = -d / 2.0f;
}
else
{
nearval = d * ((c - 1.0f) / (c + 1.0f) - 1.0f) / (-2.0f * (c - 1.0f) / (c + 1.0f));
farval = nearval * ((c - 1.0f) / (c + 1.0f));
}
left = (a - 1.0f) * nearval / x;
right = 2.0f * nearval / x + left;
bottom = (b - 1.0f) * nearval / y;
top = 2.0f * nearval / y + bottom;
}
Matrix4::Matrix4(float r1c1, float r1c2, float r1c3, float r1c4, float r2c1, float r2c2, float r2c3, float r2c4, float r3c1, float r3c2, float r3c3,
float r3c4, float r4c1, float r4c2, float r4c3, float r4c4)
{
elt[0][0] = r1c1;
elt[0][1] = r1c2;
elt[0][2] = r1c3;
elt[0][3] = r1c4;
elt[1][0] = r2c1;
elt[1][1] = r2c2;
elt[1][2] = r2c3;
elt[1][3] = r2c4;
elt[2][0] = r3c1;
elt[2][1] = r3c2;
elt[2][2] = r3c3;
elt[2][3] = r3c4;
elt[3][0] = r4c1;
elt[3][1] = r4c2;
elt[3][2] = r4c3;
elt[3][3] = r4c4;
}
/**
init should be <B>row major</B>.
*/
Matrix4::Matrix4(const float* init)
{
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
elt[r][c] = init[r * 4 + c];
}
}
}
Matrix4::Matrix4(const double* init)
{
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
elt[r][c] = (float)init[r * 4 + c];
}
}
}
Matrix4::Matrix4()
{
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
elt[r][c] = 0;
}
}
}
void Matrix4::setRow(int r, const Vector4& v)
{
for (int c = 0; c < 4; ++c)
{
elt[r][c] = v[c];
}
}
void Matrix4::setColumn(int c, const Vector4& v)
{
for (int r = 0; r < 4; ++r)
{
elt[r][c] = v[r];
}
}
const Vector4& Matrix4::row(int r) const
{
return reinterpret_cast<const Vector4*>(elt[r])[0];
}
Vector4 Matrix4::column(int c) const
{
Vector4 v;
for (int r = 0; r < 4; ++r)
{
v[r] = elt[r][c];
}
return v;
}
Matrix4 Matrix4::operator*(const Matrix4& other) const
{
Matrix4 result;
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
for (int i = 0; i < 4; ++i)
{
result.elt[r][c] += elt[r][i] * other.elt[i][c];
}
}
}
return result;
}
Matrix4 Matrix4::operator*(const float s) const
{
Matrix4 result;
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
result.elt[r][c] = elt[r][c] * s;
}
}
return result;
}
Vector3 Matrix4::homoMul(const class Vector3& v, float w) const
{
Vector4 r = (*this) * Vector4(v, w);
return r.xyz() * (1.0f / r.w);
}
Vector4 Matrix4::operator*(const Vector4& vector) const
{
Vector4 result(0, 0, 0, 0);
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
result[r] += elt[r][c] * vector[c];
}
}
return result;
}
Matrix4 Matrix4::transpose() const
{
Matrix4 result;
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
result.elt[c][r] = elt[r][c];
}
}
return result;
}
bool Matrix4::operator!=(const Matrix4& other) const
{
return !(*this == other);
}
bool Matrix4::operator==(const Matrix4& other) const
{
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
if (elt[r][c] != other.elt[r][c])
{
return false;
}
}
}
return true;
}
float Matrix4::determinant() const
{
// Determinant is the dot product of the first row and the first row
// of cofactors (i.e. the first col of the adjoint matrix)
return cofactor().row(0).dot(row(0));
}
Matrix4 Matrix4::adjoint() const
{
return cofactor().transpose();
}
Matrix4 Matrix4::inverse() const
{
// Inverse = adjoint / determinant
Matrix4 A = adjoint();
// Determinant is the dot product of the first row and the first row
// of cofactors (i.e. the first col of the adjoint matrix)
float det = A.column(0).dot(row(0));
return A * (1.0f / det);
}
Matrix4 Matrix4::cofactor() const
{
Matrix4 out;
// We'll use i to incrementally compute -1 ^ (r+c)
int i = 1;
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
// Compute the determinant of the 3x3 submatrix
float det = subDeterminant(r, c);
out.elt[r][c] = i * det;
i = -i;
}
i = -i;
}
return out;
}
float Matrix4::subDeterminant(int excludeRow, int excludeCol) const
{
// Compute non-excluded row and column indices
int row[3];
int col[3];
for (int i = 0; i < 3; ++i)
{
row[i] = i;
col[i] = i;
if (i >= excludeRow)
{
++row[i];
}
if (i >= excludeCol)
{
++col[i];
}
}
// Compute the first row of cofactors
float cofactor00 = elt[row[1]][col[1]] * elt[row[2]][col[2]] - elt[row[1]][col[2]] * elt[row[2]][col[1]];
float cofactor10 = elt[row[1]][col[2]] * elt[row[2]][col[0]] - elt[row[1]][col[0]] * elt[row[2]][col[2]];
float cofactor20 = elt[row[1]][col[0]] * elt[row[2]][col[1]] - elt[row[1]][col[1]] * elt[row[2]][col[0]];
// Product of the first row and the cofactors along the first row
return elt[row[0]][col[0]] * cofactor00 + elt[row[0]][col[1]] * cofactor10 + elt[row[0]][col[2]] * cofactor20;
}
CoordinateFrame Matrix4::approxCoordinateFrame() const
{
CoordinateFrame cframe;
for (int r = 0; r < 3; ++r)
{
for (int c = 0; c < 3; ++c)
{
cframe.rotation[r][c] = elt[r][c];
}
cframe.translation[r] = elt[r][3];
}
// Ensure that the rotation matrix is orthonormal
cframe.rotation.orthonormalize();
return cframe;
}
std::string Matrix4::toString() const
{
return G3D::format("[%g, %g, %g, %g; %g, %g, %g, %g; %g, %g, %g, %g; %g, %g, %g, %g]", elt[0][0], elt[0][1], elt[0][2], elt[0][3], elt[1][0],
elt[1][1], elt[1][2], elt[1][3], elt[2][0], elt[2][1], elt[2][2], elt[2][3], elt[3][0], elt[3][1], elt[3][2], elt[3][3]);
}
} // namespace G3D

233
engine/3d/src/Matrix4.hpp Normal file
View File

@@ -0,0 +1,233 @@
/**
@file Matrix4.h
4x4 matrix class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-10-02
@edited 2009-10-20
*/
#ifndef G3D_Matrix4_h
#define G3D_Matrix4_h
#ifdef _MSC_VER
// Disable conditional expression is constant, which occurs incorrectly on inlined functions
#pragma warning(push)
#pragma warning(disable : 4127)
#endif
#include "platform.hpp"
#include "debugAssert.hpp"
#include "Matrix3.hpp"
#include "Vector3.hpp"
namespace G3D
{
/**
A 4x4 matrix.
See also G3D::CoordinateFrame, G3D::Matrix3, G3D::Quat
*/
class Matrix4
{
private:
float elt[4][4];
/**
Computes the determinant of the 3x3 matrix that lacks excludeRow
and excludeCol.
*/
float subDeterminant(int excludeRow, int excludeCol) const;
// Hidden operators
bool operator<(const Matrix4&) const;
bool operator>(const Matrix4&) const;
bool operator<=(const Matrix4&) const;
bool operator>=(const Matrix4&) const;
public:
Matrix4(float r1c1, float r1c2, float r1c3, float r1c4, float r2c1, float r2c2, float r2c3, float r2c4, float r3c1, float r3c2, float r3c3,
float r3c4, float r4c1, float r4c2, float r4c3, float r4c4);
/**
init should be <B>row major</B>.
*/
Matrix4(const float* init);
/**
a is the upper left 3x3 submatrix and b is the upper right 3x1 submatrix. The last row of the created matrix is (0,0,0,1).
*/
Matrix4(const class Matrix3& upper3x3, const class Vector3& lastCol = Vector3::zero());
Matrix4(const class CoordinateFrame& c);
Matrix4(const double* init);
Matrix4();
/** Produces an RT transformation that nearly matches this Matrix4.
Because a Matrix4 may not be precisely a rotation and translation,
this may introduce error. */
class CoordinateFrame approxCoordinateFrame() const;
// Special values.
// Intentionally not inlined: see Matrix3::identity() for details.
static const Matrix4& identity();
static const Matrix4& zero();
/** If this is a perspective projection matrix created by
Matrix4::perspectiveProjection, extract its parameters. */
void getPerspectiveProjectionParameters(
float& left, float& right, float& bottom, float& top, float& nearval, float& farval, float updirection = -1.0f) const;
inline float* operator[](int r)
{
debugAssert(r >= 0);
debugAssert(r < 4);
return (float*)&elt[r];
}
inline const float* operator[](int r) const
{
debugAssert(r >= 0);
debugAssert(r < 4);
return (const float*)&elt[r];
}
inline operator float*()
{
return (float*)&elt[0][0];
}
inline operator const float*() const
{
return (const float*)&elt[0][0];
}
Matrix4 operator*(const Matrix4& other) const;
Matrix4 operator+(const Matrix4& other) const
{
Matrix4 result;
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
result.elt[r][c] = elt[r][c] + other.elt[r][c];
}
}
return result;
}
class Matrix3 upper3x3() const;
/** Homogeneous multiplication. Let k = M * [v w]^T. result = k.xyz() / k.w */
class Vector3 homoMul(const class Vector3& v, float w) const;
/**
Constructs an orthogonal projection matrix from the given parameters.
Near and far are the <b>NEGATIVE</b> of the near and far plane Z values
(to follow OpenGL conventions).
\param upDirection Use -1.0 for 2D Y increasing downwards (the G3D 8.x default convention),
1.0 for 2D Y increasing upwards (the G3D 7.x default and OpenGL convention)
*/
static Matrix4 orthogonalProjection(float left, float right, float bottom, float top, float nearval, float farval, float upDirection = -1.0f);
/** \param upDirection Use -1.0 for 2D Y increasing downwards (the G3D 8.x default convention),
1.0 for 2D Y increasing upwards (the G3D 7.x default and OpenGL convention)
*/
static Matrix4 orthogonalProjection(const class Rect2D& rect, float nearval, float farval, float upDirection = -1.0f);
/** \param upDirection Use -1.0 for 2D Y increasing downwards (the G3D 8.x default convention),
1.0 for 2D Y increasing upwards (the G3D 7.x default and OpenGL convention)
*/
static Matrix4 perspectiveProjection(float left, float right, float bottom, float top, float nearval, float farval, float upDirection = -1.0f);
void setRow(int r, const class Vector4& v);
void setColumn(int c, const Vector4& v);
const Vector4& row(int r) const;
Vector4 column(int c) const;
Matrix4 operator*(const float s) const;
Vector4 operator*(const Vector4& vector) const;
Matrix4 transpose() const;
bool operator!=(const Matrix4& other) const;
bool operator==(const Matrix4& other) const;
float determinant() const;
Matrix4 inverse() const;
/**
Transpose of the cofactor matrix (used in computing the inverse).
Note: This is in fact only one type of adjoint. More generally,
an adjoint of a matrix is any mapping of a matrix which possesses
certain properties. This returns the so-called adjugate
or classical adjoint.
*/
Matrix4 adjoint() const;
Matrix4 cofactor() const;
std::string toString() const;
/** 3D scale matrix */
inline static Matrix4 scale(const Vector3& v)
{
return Matrix4(v.x, 0, 0, 0, 0, v.y, 0, 0, 0, 0, v.z, 0, 0, 0, 0, 1);
}
/** 3D scale matrix */
inline static Matrix4 scale(float x, float y, float z)
{
return scale(Vector3(x, y, z));
}
/** 3D scale matrix */
inline static Matrix4 scale(float s)
{
return scale(s, s, s);
}
/** 3D translation matrix */
inline static Matrix4 translation(const Vector3& v)
{
return Matrix4(Matrix3::identity(), v);
}
inline static Matrix4 translation(float x, float y, float z)
{
return Matrix4(Matrix3::identity(), Vector3(x, y, z));
}
/** Create a rotation matrix that rotates \a deg degrees around the Y axis */
inline static Matrix4 yawDegrees(float deg)
{
return Matrix4(Matrix3::fromAxisAngle(Vector3::unitY(), toRadians(deg)));
}
inline static Matrix4 pitchDegrees(float deg)
{
return Matrix4(Matrix3::fromAxisAngle(Vector3::unitX(), toRadians(deg)));
}
inline static Matrix4 rollDegrees(float deg)
{
return Matrix4(Matrix3::fromAxisAngle(Vector3::unitZ(), toRadians(deg)));
}
};
} // namespace G3D
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif

View File

@@ -0,0 +1,48 @@
/**
@file MemoryManager.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2009-04-20
@edited 2009-05-29
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#include "MemoryManager.hpp"
#include "System.hpp"
namespace G3D
{
MemoryManager::MemoryManager() {}
void* MemoryManager::alloc(size_t s)
{
return System::malloc(s);
}
void MemoryManager::free(void* ptr)
{
System::free(ptr);
}
bool MemoryManager::isThreadsafe() const
{
return true;
}
MemoryManager* MemoryManager::create()
{
static MemoryManager m;
return &m;
}
///////////////////////////////////////////////////
} // namespace G3D

View File

@@ -0,0 +1,54 @@
/**
@file MemoryManager.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2009-04-20
@edited 2009-04-20
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_MemoryManager_h
#define G3D_MemoryManager_h
#include <stdlib.h>
#include "platform.hpp"
namespace G3D
{
/**
Abstraction of memory management.
Default implementation uses G3D::System::malloc and is threadsafe.
\sa CRTMemoryManager, AlignedMemoryManager, AreaMemoryManager */
class MemoryManager
{
protected:
MemoryManager();
public:
typedef MemoryManager* Ref;
/** Return a pointer to \a s bytes of memory that are unused by
the rest of the program. The contents of the memory are
undefined */
virtual void* alloc(size_t s);
/** Invoke to declare that this memory will no longer be used by
the program. The memory manager is not required to actually
reuse or release this memory. */
virtual void free(void* ptr);
/** Returns true if this memory manager is threadsafe (i.e., alloc
and free can be called asychronously) */
virtual bool isThreadsafe() const;
/** Return the instance. There's only one instance of the default
MemoryManager; it is cached after the first creation. */
static MemoryManager* create();
};
} // namespace G3D
#endif

631
engine/3d/src/MeshAlg.hpp Normal file
View File

@@ -0,0 +1,631 @@
/**
@file MeshAlg.h
Indexed Mesh algorithms.
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-09-14
@edited 2010-01-18
*/
#ifndef G3D_MeshAlg_h
#define G3D_MeshAlg_h
#include "platform.hpp"
#include "Array.hpp"
#include "Vector3.hpp"
#include "CoordinateFrame.hpp"
#include "SmallArray.hpp"
#include "constants.hpp"
#include "Image1.hpp"
#ifdef G3D_WIN32
// Turn off "conditional expression is constant" warning; MSVC generates this
// for debug assertions in inlined methods.
#pragma warning(push)
#pragma warning(disable : 4127)
#endif
namespace G3D
{
/**
Indexed <B>mesh alg</B>orithms. You have to build your own mesh class.
<P>
No mesh class is provided with G3D because there isn't an "ideal"
mesh format-- one application needs keyframed animation, another
skeletal animation, a third texture coordinates, a fourth
cannot precompute information, etc. Instead of compromising, this
class implements the hard parts of mesh computation and you can write
your own ideal mesh class on top of it.
\sa G3D::ArticulatedModel, G3D::IFSModel
*/
class MeshAlg
{
public:
/** \deprecated */
typedef PrimitiveType Primitive;
/** Adjacency information for a vertex.
Does not contain the vertex position or normal,
which are stored in the MeshAlg::Geometry object.
<CODE>Vertex</CODE>s must be stored in an array
parallel to (indexed in the same way as)
MeshAlg::Geometry::vertexArray.
*/
class Vertex
{
public:
Vertex() {}
/**
Array of edges adjacent to this vertex.
Let e = edgeIndex[i].
edge[(e >= 0) ? e : ~e].vertexIndex[0] == this
vertex index.
Edges may be listed multiple times if they are
degenerate.
*/
SmallArray<int, 6> edgeIndex;
/**
Returns true if e or ~e is in the edgeIndex list.
*/
inline bool inEdge(int e) const
{
return edgeIndex.contains(~e) || edgeIndex.contains(e);
}
/**
Array of faces containing this vertex. Faces
may be listed multiple times if they are degenerate.
*/
SmallArray<int, 6> faceIndex;
inline bool inFace(int f) const
{
debugAssert(f >= 0);
return faceIndex.contains(f);
}
};
/**
Oriented, indexed triangle.
*/
class Face
{
public:
Face();
/**
Used by Edge::faceIndex to indicate a missing face.
This is a large negative value.
*/
static const int NONE;
/**
Vertices in the face in counter-clockwise order.
Degenerate faces may include the same vertex multiple times.
*/
int vertexIndex[3];
inline bool containsVertex(int v) const
{
return contains(vertexIndex, 3, v);
}
/**
Edge indices in counter-clockwise order. Edges are
undirected, so it is important to know which way
each edge is pointing in a face. This is encoded
using negative indices.
If <CODE>edgeIndex[i] >= 0</CODE> then this face
contains the directed edge
between vertex indices
<CODE>edgeArray[face.edgeIndex[i]].vertexIndex[0]</CODE>
and
<CODE>edgeArray[face.edgeIndex[i]].vertexIndex[1]</CODE>.
If <CODE>edgeIndex[i] < 0</CODE> then
<CODE>~edgeIndex[i]</CODE> (i.e. the two's
complement of) is used and this face contains the directed
edge between vertex indices
<CODE>edgeArray[~face.edgeIndex[i]].vertexIndex[0]</CODE>
and
<CODE>edgeArray[~face.edgeIndex[i]].vertexIndex[1]</CODE>.
Degenerate faces may include the same edge multiple times.
*/
// Temporarily takes on the value Face::NONE during adjacency
// computation to indicate an edge that has not yet been assigned.
int edgeIndex[3];
inline bool containsEdge(int e) const
{
if (e < 0)
{
e = ~e;
}
return contains(edgeIndex, 3, e) || contains(edgeIndex, 3, ~e);
}
/** Contains the forward edge e if e >= 0 and the backward edge
~e otherwise. */
inline bool containsDirectedEdge(int e) const
{
return contains(edgeIndex, 3, e);
}
};
/** Oriented, indexed edge */
class Edge
{
public:
Edge();
/** Degenerate edges may include the same vertex times. */
int vertexIndex[2];
inline bool containsVertex(int v) const
{
return contains(vertexIndex, 2, v);
}
/**
The edge is directed <B>forward</B> in face 0
<B>backward</B> in face 1. Face index of MeshAlg::Face::NONE
indicates a boundary (a.k.a. crack, broken) edge.
*/
int faceIndex[2];
/** Returns true if f is contained in the faceIndex array in either slot.
To see if it is forward in that face, just check edge.faceIndex[0] == f.*/
inline bool inFace(int f) const
{
return contains(faceIndex, 2, f);
}
/**
Returns true if either faceIndex is NONE.
*/
inline bool boundary() const
{
return (faceIndex[0] == Face::NONE) || (faceIndex[1] == Face::NONE);
}
/**
Returns the reversed edge.
*/
inline Edge reverse() const
{
Edge e;
e.vertexIndex[0] = vertexIndex[1];
e.vertexIndex[1] = vertexIndex[0];
e.faceIndex[0] = faceIndex[1];
e.faceIndex[1] = faceIndex[0];
return e;
}
};
/**
Convenient for passing around the per-vertex data that changes under
animation. The faces and edges are needed to interpret
these values.
*/
class Geometry
{
public:
/** Vertex positions */
Array<Vector3> vertexArray;
/** Vertex normals */
Array<Vector3> normalArray;
/**
Assignment is optimized using SSE.
*/
Geometry& operator=(const Geometry& src);
void clear()
{
vertexArray.clear();
normalArray.clear();
}
};
/**
Given a set of vertices and a set of indices for traversing them
to create triangles, computes other mesh properties.
<B>Colocated vertices are treated as separate.</B> To have
colocated vertices collapsed (necessary for many algorithms,
like shadowing), weld the mesh before computing adjacency.
<I>Recent change: In version 6.00, colocated vertices were automatically
welded by this routine and degenerate faces and edges were removed. That
is no longer the case.</I>
Where two faces meet, there are two opposite directed edges. These
are collapsed into a single bidirectional edge in the edgeArray.
If four faces meet exactly at the same edge, that edge will appear
twice in the array, and so on. If an edge is a boundary of the mesh
(i.e. if the edge has only one adjacent face) it will appear in the
array with one face index set to MeshAlg::Face::NONE.
@param vertexGeometry %Vertex positions to use when deciding colocation.
@param indexArray Order to traverse vertices to make triangles
@param faceArray <I>Output</I>
@param edgeArray <I>Output</I>. Sorted so that boundary edges are at the end of the array.
@param vertexArray <I>Output</I>
*/
static void computeAdjacency(const Array<Vector3>& vertexGeometry, const Array<int>& indexArray, Array<Face>& faceArray, Array<Edge>& edgeArray,
Array<Vertex>& vertexArray);
/**
@deprecated Use the other version of computeAdjacency, which takes Array<Vertex>.
@param facesAdjacentToVertex <I>Output</I> adjacentFaceArray[v] is an array of
indices for faces touching vertex index v
*/
static void computeAdjacency(const Array<Vector3>& vertexArray, const Array<int>& indexArray, Array<Face>& faceArray, Array<Edge>& edgeArray,
Array<Array<int>>& facesAdjacentToVertex);
/**
Computes some basic mesh statistics including: min, max mean and median,
edge lengths; and min, mean, median, and max face area.
@param vertexArray %Vertex positions to use when deciding colocation.
@param indexArray Order to traverse vertices to make triangles
@param minEdgeLength Minimum edge length
@param meanEdgeLength Mean edge length
@param medianEdgeLength Median edge length
@param maxEdgeLength Max edge length
@param minFaceArea Minimum face area
@param meanFaceArea Mean face area
@param medianFaceArea Median face area
@param maxFaceArea Max face area
*/
static void computeAreaStatistics(const Array<Vector3>& vertexArray, const Array<int>& indexArray, double& minEdgeLength, double& meanEdgeLength,
double& medianEdgeLength, double& maxEdgeLength, double& minFaceArea, double& meanFaceArea, double& medianFaceArea, double& maxFaceArea);
private:
/** Helper for weldAdjacency */
static void weldBoundaryEdges(Array<Face>& faceArray, Array<Edge>& edgeArray, Array<Vertex>& vertexArray);
public:
/**
Computes tangent and binormal vectors,
which provide a (mostly) consistent
parameterization over the surface for
effects like bump mapping. In the resulting coordinate frame,
T = x (varies with texture s coordinate), B = y (varies with negative texture t coordinate),
and N = z for a right-handed coordinate frame. If a billboard is vertical on the screen
in view of the camera, the tangent space matches the camera's coordinate frame.
The vertex, texCoord, tangent, and binormal
arrays are parallel arrays.
The resulting tangent and binormal might not be exactly
perpendicular to each other. They are guaranteed to
be perpendicular to the normal.
@cite Max McGuire
*/
static void computeTangentSpaceBasis(const Array<Vector3>& vertexArray, const Array<Vector2>& texCoordArray,
const Array<Vector3>& vertexNormalArray, const Array<Face>& faceArray, Array<Vector3>& tangent, Array<Vector3>& binormal);
/** @deprecated */
static void computeNormals(const Array<Vector3>& vertexArray, const Array<Face>& faceArray, const Array<Array<int>>& adjacentFaceArray,
Array<Vector3>& vertexNormalArray, Array<Vector3>& faceNormalArray);
/**
Vertex normals are weighted by the area of adjacent faces.
Nelson Max showed this is superior to uniform weighting for
general meshes in jgt.
@param vertexNormalArray Output. Unit length
@param faceNormalArray Output. Degenerate faces produce zero magnitude normals. Unit length
@see weld
*/
static void computeNormals(const Array<Vector3>& vertexGeometry, const Array<Face>& faceArray, const Array<Vertex>& vertexArray,
Array<Vector3>& vertexNormalArray, Array<Vector3>& faceNormalArray);
/** Computes unit length normals in place using the other computeNormals methods.
If you already have a face array use another method; it will be faster.
@see weld*/
static void computeNormals(Geometry& geometry, const Array<int>& indexArray);
/**
Computes face normals only. Significantly faster (especially if
normalize is false) than computeNormals.
@see weld
*/
static void computeFaceNormals(
const Array<Vector3>& vertexArray, const Array<Face>& faceArray, Array<Vector3>& faceNormals, bool normalize = true);
/**
Classifies each face as a backface or a front face relative
to the observer point P (which is at infinity when P.w = 0).
A face with normal exactly perpendicular to the observer vector
may be classified as either a front or a back face arbitrarily.
*/
static void identifyBackfaces(const Array<Vector3>& vertexArray, const Array<Face>& faceArray, const Vector4& P, Array<bool>& backface);
/** A faster version of identifyBackfaces for the case where
face normals have already been computed */
static void identifyBackfaces(
const Array<Vector3>& vertexArray, const Array<Face>& faceArray, const Vector4& P, Array<bool>& backface, const Array<Vector3>& faceNormals);
/**
Welds nearby and colocated elements of the <I>oldVertexArray</I> together so that
<I>newVertexArray</I> contains no vertices within <I>radius</I> of one another.
Every vertex in newVertexPositions also appears in oldVertexPositions.
This is useful for downsampling meshes and welding cracks created by artist errors
or numerical imprecision.
The two integer arrays map indices back and forth between the arrays according to:
<PRE>
oldVertexArray[toOld[ni]] == newVertexArray[ni]
oldVertexArray[oi] == newVertexArray[toNew[ni]]
</PRE>
Note that newVertexPositions is never longer than oldVertexPositions
and is shorter when vertices are welded.
Welding with a large radius will effectively compute a lower level of detail for
the mesh.
The welding method runs in roughly linear time in the length of oldVertexArray--
a uniform spatial grid is used to achieve nearly constant time vertex collapses
for uniformly distributed vertices.
It is sometimes desirable to keep the original vertex ordering but
identify the unique vertices. The following code computes
array canonical s.t. canonical[v] = first occurance of
a vertex near oldVertexPositions[v] in oldVertexPositions.
<PRE>
Array<int> canonical(oldVertexPositions.size()), toNew, toOld;
computeWeld(oldVertexPositions, Array<Vector3>(), toNew, toOld, radius);
for (int v = 0; v < canonical.size(); ++v) {
canonical[v] = toOld[toNew[v]];
}
</PRE>
See also G3D::MeshAlg::weldAdjacency.
@cite The method is that described as the 'Grouper' in Baum, Mann, Smith, and Winget,
Making Radiosity Usable: Automatic Preprocessing and Meshing Techniques for
the Generation of Accurate Radiosity Solutions, Computer Graphics vol 25, no 4, July 1991.
@deprecated Use weld.
*/
static void computeWeld(const Array<Vector3>& oldVertexPositions, Array<Vector3>& newVertexPositions, Array<int>& toNew, Array<int>& toOld,
double radius = fuzzyEpsilon);
/**
Modifies the face, edge, and vertex arrays in place so that
colocated (within radius) vertices are treated as identical.
Note that the vertexArray and corresponding geometry will
contain elements that are no longer used. In the vertexArray,
these elements are initialized to MeshAlg::Vertex() but not
removed (because removal would change the indexing).
This is a good preprocessing step for algorithms that are only
concerned with the shape of a mesh (e.g. cartoon rendering, fur, shadows)
and not the indexing of the vertices.
Use this method when you have already computed adjacency information
and want to collapse colocated vertices within that data without
disturbing the actual mesh vertices or indexing scheme.
If you have not computed adjacency already, use MeshAlg::computeWeld
instead and compute adjacency information after welding.
@deprecated Use weld.
@param faceArray Mutated in place. Size is maintained (degenerate
faces are <b>not</B> removed).
@param edgeArray Mutated in place. May shrink if boundary edges
are welded together.
@param vertexArray Mutated in place. Size is maintained (duplicate
vertices contain no adjacency info).
*/
static void weldAdjacency(const Array<Vector3>& originalGeometry, Array<Face>& faceArray, Array<Edge>& edgeArray, Array<Vertex>& vertexArray,
double radius = fuzzyEpsilon);
/**
Counts the number of edges (in an edge array returned from
MeshAlg::computeAdjacency) that have only one adjacent face.
*/
static int countBoundaryEdges(const Array<Edge>& edgeArray);
/**
Generates an array of integers from start to start + n - 1 that have run numbers
in series then omit the next skip before the next run. Useful for turning
a triangle list into an indexed face set.
Example:
<PRE>
createIndexArray(10, x);
// x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
createIndexArray(5, x, 2);
// x = [2, 3, 4, 5, 6, 7]
createIndexArray(6, x, 0, 2, 1);
// x = [0, 1, 3, 4, 6, 7]
</PRE>
*/
static void createIndexArray(int n, Array<int>& array, int start = 0, int run = 1, int skip = 0);
/**
Computes a conservative, near-optimal axis aligned bounding box and sphere.
@cite The bounding sphere uses the method from J. Ritter. An effcient bounding sphere. In Andrew S. Glassner, editor, Graphics Gems. Academic
Press, Boston, MA, 1990.
*/
static void computeBounds(const Array<Vector3>& vertex, class AABox& box, class Sphere& sphere);
/** Computes bounds for a subset of the vertices. It is ok if vertices appear more than once in the index array. */
static void computeBounds(const Array<Vector3>& vertex, const Array<int>& index, class AABox& box, class Sphere& sphere);
/**
In debug mode, asserts that the adjacency references between the
face, edge, and vertex array are consistent.
*/
static void debugCheckConsistency(const Array<Face>& faceArray, const Array<Edge>& edgeArray, const Array<Vertex>& vertexArray);
/**
Generates a unit square in the X-Z plane composed of a grid of wCells x hCells
squares and then transforms it by xform.
@param vertex Output vertices
@param texCoord Output texture coordinates
@param index Output triangle list indices
@param textureScale Lower-right texture coordinate
@param spaceCentered If true, the coordinates generated are centered at the origin before the transformation.
@param twoSided If true, matching top and bottom planes are generated.
\param elevation If non-NULL, values from this image are used as elevations. Apply an \a xform to adjust the scale
*/
static void generateGrid(Array<Vector3>& vertex, Array<Vector2>& texCoord, Array<int>& index, int wCells = 10, int hCells = 10,
const Vector2& textureScale = Vector2(1, 1), bool spaceCentered = true, bool twoSided = true,
const CoordinateFrame& xform = CoordinateFrame(), const Image1::Ref& elevation = NULL);
/** Converts quadlist (QUADS),
triangle fan (TRIANGLE_FAN),
tristrip(TRIANGLE_STRIP), and quadstrip (QUAD_STRIP) indices into
triangle list (TRIANGLES) indices and appends them to outIndices. */
template<class IndexType>
static void toIndexedTriList(const Array<IndexType>& inIndices, MeshAlg::Primitive inType, Array<IndexType>& outIndices)
{
debugAssert(inType == PrimitiveType::TRIANGLE_STRIP || inType == PrimitiveType::TRIANGLE_FAN || inType == PrimitiveType::QUADS ||
inType == PrimitiveType::QUAD_STRIP);
const int inSize = inIndices.size();
switch (inType)
{
case PrimitiveType::TRIANGLE_FAN:
{
debugAssert(inSize >= 3);
int N = outIndices.size();
outIndices.resize(N + (inSize - 2) * 3);
for (IndexType i = 1, outIndex = N; i <= (inSize - 2); ++i, outIndex += 3)
{
outIndices[outIndex] = inIndices[0];
outIndices[outIndex + 1] = inIndices[i];
outIndices[outIndex + 2] = inIndices[i + 1];
}
break;
}
case PrimitiveType::TRIANGLE_STRIP:
{
debugAssert(inSize >= 3);
int N = outIndices.size();
outIndices.resize(N + (inSize - 2) * 3);
bool atEven = false;
for (IndexType i = 0, outIndex = N; i < (inSize - 2); ++i, outIndex += 3)
{
if (atEven)
{
outIndices[outIndex] = inIndices[i + 1];
outIndices[outIndex + 1] = inIndices[i];
outIndices[outIndex + 2] = inIndices[i + 2];
atEven = false;
}
else
{
outIndices[outIndex] = inIndices[i];
outIndices[outIndex + 1] = inIndices[i + 1];
outIndices[outIndex + 2] = inIndices[i + 2];
atEven = true;
}
}
break;
}
case PrimitiveType::QUADS:
{
debugAssert(inIndices.size() >= 4);
int N = outIndices.size();
outIndices.resize(N + (inSize / 4) * 3);
for (IndexType i = 0, outIndex = N; i <= (inSize - 4); i += 4, outIndex += 6)
{
outIndices[outIndex] = inIndices[i];
outIndices[outIndex + 1] = inIndices[i + 1];
outIndices[outIndex + 2] = inIndices[i + 3];
outIndices[outIndex + 3] = inIndices[i + 1];
outIndices[outIndex + 4] = inIndices[i + 2];
outIndices[outIndex + 5] = inIndices[i + 3];
}
break;
}
case PrimitiveType::QUAD_STRIP:
{
debugAssert(inIndices.size() >= 4);
int N = outIndices.size();
outIndices.resize(N + (inSize - 2) * 3);
for (IndexType i = 0, outIndex = N; i <= (inSize - 2); i += 2, outIndex += 6)
{
outIndices[outIndex] = inIndices[i];
outIndices[outIndex + 1] = inIndices[i + 1];
outIndices[outIndex + 2] = inIndices[i + 2];
outIndices[outIndex + 3] = inIndices[i + 2];
outIndices[outIndex + 4] = inIndices[i + 1];
outIndices[outIndex + 5] = inIndices[i + 3];
}
break;
}
default:
alwaysAssertM(false, "Illegal argument");
}
}
protected:
/**
Helper for computeAdjacency. If a directed edge with index e already
exists from i0 to i1 then e is returned. If a directed edge with index e
already exists from i1 to i0, ~e is returned (the complement) and
edgeArray[e] is set to f. Otherwise, a new edge is created from i0 to i1
with first face index f and its index is returned.
@param vertexArray Vertex positions to use when deciding colocation.
@param area Area of face f. When multiple edges of the same direction
are found between the same vertices (usually because of degenerate edges)
the face with larger area is kept in the edge table.
*/
static int findEdgeIndex(const Array<Vector3>& vertexArray, Array<Edge>& geometricEdgeArray, int i0, int i1, int f, double area);
};
} // namespace G3D
#ifdef G3D_WIN32
#pragma warning(pop)
#endif
#endif

View File

@@ -0,0 +1,90 @@
/**
@file MeshBuilder.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-02-27
@edited 2004-10-04
*/
#ifndef G3D_MESHBUILDER_H
#define G3D_MESHBUILDER_H
#include "platform.hpp"
#include "Array.hpp"
#include "Vector3.hpp"
#include "Triangle.hpp"
namespace G3D
{
/**
Allows creation of optimized watertight meshes from unoptimized polygon soups.
See also G3D::MeshAlg for algorithms that operate on the output.
*/
class MeshBuilder
{
public:
/**
Set setWeldRadius to AUTO_WELD to weld vertices closer than 1/2
the smallest edge length in a model.
*/
enum
{
AUTO_WELD = -100
};
private:
/** Indices of vertices in <B>or near</B> a grid cell. */
typedef Array<int> List;
std::string name;
/**
All of the triangles, as a long triangle list.
*/
Array<Vector3> triList;
void centerTriList();
void computeBounds(Vector3& min, Vector3& max);
bool _twoSided;
/** Collapse radius */
double close;
public:
inline MeshBuilder(bool twoSided = false)
: _twoSided(twoSided)
, close(AUTO_WELD)
{
}
/** Writes the model to the arrays, which can then be used with
G3D::IFSModel::save and G3D::MeshAlg */
void commit(std::string& name, Array<int>& indexArray, Array<Vector3>& vertexArray);
/**
Adds a new triangle to the model. (Counter clockwise)
*/
void addTriangle(const Vector3& a, const Vector3& b, const Vector3& c);
/**
Adds two new triangles to the model. (Counter clockwise)
*/
void addQuad(const Vector3& a, const Vector3& b, const Vector3& c, const Vector3& d);
void addTriangle(const Triangle& t);
void setName(const std::string& n);
/** Vertices within this distance are considered identical.
Use AUTO_WELD (the default) to have the distance be a function of the model size.*/
void setWeldRadius(double r)
{
close = r;
}
};
} // namespace G3D
#endif

View File

@@ -0,0 +1,81 @@
/**
@file ParseError.h
@maintainer Morgan McGuire
@created 2009-11-15
@edited 2009-11-15
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_ParseError_h
#define G3D_ParseError_h
#include "platform.hpp"
#include "g3dmath.hpp"
#include <string>
namespace G3D
{
/** Thrown by TextInput, Any, and other parsers on unexpected input. */
class ParseError
{
public:
enum
{
UNKNOWN = -1
};
/** Empty means unknown */
std::string filename;
/** For a binary file, the location of the parse error. -1 if unknown.*/
int64 byte;
/** For a text file, the line number is the line number of start of token which caused the exception. 1 is
the first line of the file. -1 means unknown. Note that you can use
TextInput::Settings::startingLineNumberOffset to shift the effective line
number that is reported by that class.
*/
int line;
/** Character number (in the line) of the start of the token which caused the
exception. 1 is the character in the line. May be -1 if unknown.
*/
int character;
std::string message;
ParseError()
: byte(UNKNOWN)
, line(UNKNOWN)
, character(UNKNOWN)
{
}
virtual ~ParseError() {}
ParseError(const std::string& f, int l, int c, const std::string& m)
: filename(f)
, byte(UNKNOWN)
, line(l)
, character(c)
, message(m)
{
}
ParseError(const std::string& f, int64 b, const std::string& m)
: filename(f)
, byte(b)
, line(UNKNOWN)
, character(UNKNOWN)
, message(m)
{
}
};
} // namespace G3D
#endif

View File

@@ -0,0 +1,66 @@
/**
@file PhysicsFrame.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-07-09
@edited 2010-03-25
*/
#include "platform.hpp"
#include "stringutils.hpp"
#include "PhysicsFrame.hpp"
namespace G3D
{
PhysicsFrame::PhysicsFrame()
{
translation = Vector3::zero();
rotation = Quat();
}
PhysicsFrame::PhysicsFrame(const CoordinateFrame& coordinateFrame)
{
translation = coordinateFrame.translation;
rotation = Quat(coordinateFrame.rotation);
}
PhysicsFrame PhysicsFrame::operator*(const PhysicsFrame& other) const
{
PhysicsFrame result;
result.rotation = rotation * other.rotation;
result.translation = translation + rotation.toRotationMatrix() * other.translation;
return result;
}
PhysicsFrame::operator CoordinateFrame() const
{
CoordinateFrame f;
f.translation = translation;
f.rotation = rotation.toRotationMatrix();
return f;
}
PhysicsFrame PhysicsFrame::lerp(const PhysicsFrame& other, float alpha) const
{
PhysicsFrame result;
result.translation = translation.lerp(other.translation, alpha);
result.rotation = rotation.slerp(other.rotation, alpha);
return result;
}
}; // namespace G3D

View File

@@ -0,0 +1,115 @@
/**
@file PhysicsFrame.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-07-08
@edited 2006-01-10
*/
#ifndef G3D_PHYSICSFRAME_H
#define G3D_PHYSICSFRAME_H
#include "platform.hpp"
#include "Vector3.hpp"
#include "Matrix3.hpp"
#include "Quat.hpp"
#include "CoordinateFrame.hpp"
#include <math.h>
#include <string>
namespace G3D
{
/**
An RT transformation using a quaternion; suitable for
physics integration.
This interface is in "Beta" and will change in the next release.
*/
class PhysicsFrame
{
public:
Quat rotation;
/**
Takes object space points to world space.
*/
Vector3 translation;
/**
Initializes to the identity frame.
*/
PhysicsFrame();
/**
Purely translational.
*/
PhysicsFrame(const Vector3& translation)
: translation(translation)
{
}
PhysicsFrame(const Quat& rot, const Vector3& translation)
: rotation(rot)
, translation(translation)
{
}
PhysicsFrame(const Matrix3& rot, const Vector3& translation)
: rotation(rot)
, translation(translation)
{
}
PhysicsFrame(const Matrix3& rot)
: rotation(rot)
, translation(Vector3::zero())
{
}
PhysicsFrame(const CoordinateFrame& coordinateFrame);
/** Compose: create the transformation that is <I>other</I> followed by <I>this</I>.*/
PhysicsFrame operator*(const PhysicsFrame& other) const;
virtual ~PhysicsFrame() {}
/**
Linear interpolation (spherical linear for the rotations).
*/
PhysicsFrame lerp(const PhysicsFrame& other, float alpha) const;
operator CFrame() const;
/** Multiplies both pieces by \a f; note that this will result in a non-unit
quaternion that needs to be normalized */
PhysicsFrame& operator*=(float f)
{
rotation *= f;
translation *= f;
return *this;
}
/** Multiplies both pieces by \a f; note that this will result in a non-unit
quaternion that needs to be normalized */
PhysicsFrame operator*(float f) const
{
return PhysicsFrame(rotation * f, translation * f);
}
PhysicsFrame operator+(const PhysicsFrame& f) const
{
return PhysicsFrame(rotation + f.rotation, translation + f.translation);
}
PhysicsFrame& operator+=(const PhysicsFrame& f)
{
rotation += f.rotation;
translation += f.translation;
return *this;
}
};
typedef PhysicsFrame PFrame;
} // namespace G3D
#endif

134
engine/3d/src/Plane.cpp Normal file
View File

@@ -0,0 +1,134 @@
/**
@file Plane.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-02-06
@edited 2006-01-29
*/
#include "platform.hpp"
#include "Plane.hpp"
#include "stringutils.hpp"
namespace G3D
{
Plane::Plane(Vector4 point0, Vector4 point1, Vector4 point2)
{
debugAssertM(point0.w != 0 || point1.w != 0 || point2.w != 0, "At least one point must be finite.");
// Rotate the points around so that the finite points come first.
while ((point0.w == 0) && ((point1.w == 0) || (point2.w != 0)))
{
Vector4 temp = point0;
point0 = point1;
point1 = point2;
point2 = temp;
}
Vector3 dir1;
Vector3 dir2;
if (point1.w == 0)
{
// 1 finite, 2 infinite points; the plane must contain
// the direction of the two direcitons
dir1 = point1.xyz();
dir2 = point2.xyz();
}
else if (point2.w != 0)
{
// 3 finite points, the plane must contain the directions
// betwseen the points.
dir1 = point1.xyz() - point0.xyz();
dir2 = point2.xyz() - point0.xyz();
}
else
{
// 2 finite, 1 infinite point; the plane must contain
// the direction between the first two points and the
// direction of the third point.
dir1 = point1.xyz() - point0.xyz();
dir2 = point2.xyz();
}
_normal = dir1.cross(dir2).direction();
_distance = _normal.dot(point0.xyz());
}
Plane::Plane(const Vector3& point0, const Vector3& point1, const Vector3& point2)
{
_normal = (point1 - point0).cross(point2 - point0).direction();
_distance = _normal.dot(point0);
}
Plane::Plane(const Vector3& __normal, const Vector3& point)
{
_normal = __normal.direction();
_distance = _normal.dot(point);
}
Plane Plane::fromEquation(float a, float b, float c, float d)
{
Vector3 n(a, b, c);
float magnitude = n.magnitude();
d /= magnitude;
n /= magnitude;
return Plane(n, -d);
}
void Plane::flip()
{
_normal = -_normal;
_distance = -_distance;
}
void Plane::getEquation(Vector3& n, float& d) const
{
double _d;
getEquation(n, _d);
d = (float)_d;
}
void Plane::getEquation(Vector3& n, double& d) const
{
n = _normal;
d = -_distance;
}
void Plane::getEquation(float& a, float& b, float& c, float& d) const
{
double _a, _b, _c, _d;
getEquation(_a, _b, _c, _d);
a = (float)_a;
b = (float)_b;
c = (float)_c;
d = (float)_d;
}
void Plane::getEquation(double& a, double& b, double& c, double& d) const
{
a = _normal.x;
b = _normal.y;
c = _normal.z;
d = -_distance;
}
std::string Plane::toString() const
{
return format("Plane(%g, %g, %g, %g)", _normal.x, _normal.y, _normal.z, _distance);
}
} // namespace G3D

200
engine/3d/src/Plane.hpp Normal file
View File

@@ -0,0 +1,200 @@
/**
@file Plane.h
Plane class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2001-06-02
@edited 2004-07-18
*/
#ifndef G3D_PLANE_H
#define G3D_PLANE_H
#include "platform.hpp"
#include "Vector3.hpp"
#include "Vector4.hpp"
#include "debugAssert.hpp"
namespace G3D
{
/**
An infinite 2D plane in 3D space.
*/
class Plane
{
private:
/** normal.Dot(x,y,z) = distance */
Vector3 _normal;
float _distance;
/**
Assumes the normal has unit length.
*/
// Aya
public:
// =====
Plane(const Vector3& n, float d)
: _normal(n)
, _distance(d)
{
}
// Aya
// public:
// =====
Plane()
: _normal(Vector3::unitY())
, _distance(0)
{
}
/**
Constructs a plane from three points.
*/
Plane(const Vector3& point0, const Vector3& point1, const Vector3& point2);
/**
Constructs a plane from three points, where at most two are
at infinity (w = 0, not xyz = inf).
*/
Plane(Vector4 point0, Vector4 point1, Vector4 point2);
/**
The normal will be unitized.
*/
Plane(const Vector3& __normal, const Vector3& point);
static Plane fromEquation(float a, float b, float c, float d);
// Aya
bool operator==(const Plane& other) const
{
return ((_normal == other._normal) && (_distance == other._distance));
}
// =======
virtual ~Plane() {}
// Aya
#if 0
/**
Returns true if point is on the side the normal points to or
is in the plane.
*/
inline bool halfSpaceContains(Vector3 point) const {
// Clamp to a finite range for testing
point = point.clamp(Vector3::minFinite(), Vector3::maxFinite());
// We can get away with putting values *at* the limits of the float32 range into
// a dot product, since the dot product is carried out on float64.
return _normal.dot(point) >= _distance;
}
#else
bool halfSpaceContains(Vector3 point) const
{
point = point.clamp(Vector3::minFinite(), Vector3::maxFinite());
return _normal.dot(point) >= _distance;
}
bool pointOnOrBehind(Vector3 point) const
{
point = point.clamp(Vector3::minFinite(), Vector3::maxFinite());
return _normal.dot(point) <= _distance;
}
#endif
// ========
/**
Returns true if point is on the side the normal points to or
is in the plane.
*/
inline bool halfSpaceContains(const Vector4& point) const
{
if (point.w == 0)
{
return _normal.dot(point.xyz()) > 0;
}
else
{
return halfSpaceContains(point.xyz() / point.w);
}
}
/**
Returns true if point is on the side the normal points to or
is in the plane. Only call on finite points. Faster than halfSpaceContains.
*/
inline bool halfSpaceContainsFinite(const Vector3& point) const
{
debugAssert(point.isFinite());
return _normal.dot(point) >= _distance;
}
/**
Returns true if the point is nearly in the plane.
*/
inline bool fuzzyContains(const Vector3& point) const
{
return fuzzyEq(point.dot(_normal), _distance);
}
inline const Vector3& normal() const
{
return _normal;
}
// Aya
inline float distance() const
{
return _distance;
}
//====
/**
Returns distance from point to plane. Distance is negative if point is behind (not in plane in direction opposite normal) the plane.
*/
inline float distance(const Vector3& x) const
{
return (_normal.dot(x) - _distance);
}
inline Vector3 closestPoint(const Vector3& x) const
{
return x + (_normal * (-distance(x)));
}
/** Returns normal * distance from origin */
Vector3 center() const
{
return _normal * _distance;
}
/**
Inverts the facing direction of the plane so the new normal
is the inverse of the old normal.
*/
void flip();
/**
Returns the equation in the form:
<CODE>normal.Dot(Vector3(<I>x</I>, <I>y</I>, <I>z</I>)) + d = 0</CODE>
*/
void getEquation(Vector3& normal, double& d) const;
void getEquation(Vector3& normal, float& d) const;
/**
ax + by + cz + d = 0
*/
void getEquation(double& a, double& b, double& c, double& d) const;
void getEquation(float& a, float& b, float& c, float& d) const;
std::string toString() const;
};
} // namespace G3D
#endif

View File

@@ -0,0 +1,9 @@
#ifndef G3D_POSITIONTRAIT_H
#define G3D_POSITIONTRAIT_H
template<typename Value>
struct PositionTrait
{
};
#endif

1649
engine/3d/src/Quat.cpp Normal file

File diff suppressed because it is too large Load Diff

801
engine/3d/src/Quat.hpp Normal file
View File

@@ -0,0 +1,801 @@
/**
@file Quat.h
Quaternion
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-01-23
@edited 2009-05-10
*/
#ifndef G3D_Quat_h
#define G3D_Quat_h
#include "platform.hpp"
#include "g3dmath.hpp"
#include "Vector3.hpp"
#include "Matrix3.hpp"
#include <string>
namespace G3D
{
/**
Arbitrary quaternion (not necessarily unit)
Unit quaternions are used in computer graphics to represent
rotation about an axis. Any 3x3 rotation matrix can
be stored as a quaternion.
A quaternion represents the sum of a real scalar and
an imaginary vector: ix + jy + kz + w. A unit quaternion
representing a rotation by A about axis v has the form
[sin(A/2)*v, cos(A/2)]. For a unit quaternion, q.conj() == q.inverse()
is a rotation by -A about v. -q is the same rotation as q
(negate both the axis and angle).
A non-unit quaterion q represents the same rotation as
q.unitize() (Dam98 pg 28).
Although quaternion-vector operations (eg. Quat + Vector3) are
well defined, they are not supported by this class because
they typically are bugs when they appear in code.
Do not subclass.
<B>BETA API -- subject to change</B>
\cite Erik B. Dam, Martin Koch, Martin Lillholm, Quaternions, Interpolation and Animation. Technical Report DIKU-TR-98/5, Department of Computer
Science, University of Copenhagen, Denmark. 1998.
*/
class Quat
{
private:
// Hidden operators
bool operator<(const Quat&) const;
bool operator>(const Quat&) const;
bool operator<=(const Quat&) const;
bool operator>=(const Quat&) const;
public:
/**
q = [sin(angle / 2) * axis, cos(angle / 2)]
In Watt & Watt's notation, s = w, v = (x, y, z)
In the Real-Time Rendering notation, u = (x, y, z), w = w
*/
float x, y, z, w;
/**
Initializes to a zero degree rotation, (0,0,0,1)
*/
Quat()
: x(0)
, y(0)
, z(0)
, w(1)
{
}
Quat(const Matrix3& rot);
Quat(float _x, float _y, float _z, float _w)
: x(_x)
, y(_y)
, z(_z)
, w(_w)
{
}
/** Defaults to a pure vector quaternion */
Quat(const Vector3& v, float _w = 0)
: x(v.x)
, y(v.y)
, z(v.z)
, w(_w)
{
}
explicit Quat(const float values[4])
: x(values[0])
, y(values[1])
, z(values[2])
, w(values[3])
{
}
/**
The real part of the quaternion.
*/
const float& real() const
{
return w;
}
float& real()
{
return w;
}
Quat operator-() const
{
return Quat(-x, -y, -z, -w);
}
Quat operator-(const Quat& other) const
{
return Quat(x - other.x, y - other.y, z - other.z, w - other.w);
}
Quat& operator-=(const Quat& q)
{
x -= q.x;
y -= q.y;
z -= q.z;
w -= q.w;
return *this;
}
Quat operator+(const Quat& q) const
{
return Quat(x + q.x, y + q.y, z + q.z, w + q.w);
}
Quat& operator+=(const Quat& q)
{
x += q.x;
y += q.y;
z += q.z;
w += q.w;
return *this;
}
/**
Negates the imaginary part.
*/
Quat conj() const
{
return Quat(-x, -y, -z, w);
}
float sum() const
{
return x + y + z + w;
}
float average() const
{
return sum() / 4.0f;
}
Quat operator*(float s) const
{
return Quat(x * s, y * s, z * s, w * s);
}
Quat& operator*=(float s)
{
x *= s;
y *= s;
z *= s;
w *= s;
return *this;
}
/** @cite Based on Watt & Watt, page 360 */
friend Quat operator*(float s, const Quat& q);
inline Quat operator/(float s) const
{
return Quat(x / s, y / s, z / s, w / s);
}
float dot(const Quat& other) const
{
return (x * other.x) + (y * other.y) + (z * other.z) + (w * other.w);
}
/** Note: two quats can represent the Quat::sameRotation and not be equal. */
bool fuzzyEq(const Quat& q)
{
return G3D::fuzzyEq(x, q.x) && G3D::fuzzyEq(y, q.y) && G3D::fuzzyEq(z, q.z) && G3D::fuzzyEq(w, q.w);
}
/** True if these quaternions represent the same rotation (note that every rotation is
represented by two values; q and -q).
*/
bool sameRotation(const Quat& q)
{
return fuzzyEq(q) || fuzzyEq(-q);
}
/**
Returns the imaginary part (x, y, z)
*/
const Vector3& imag() const
{
return *(reinterpret_cast<const Vector3*>(this));
}
Vector3& imag()
{
return *(reinterpret_cast<Vector3*>(this));
}
/** q = [sin(angle/2)*axis, cos(angle/2)] */
static Quat fromAxisAngleRotation(const Vector3& axis, float angle);
static Quat fromRotation(const Vector3& rotation);
static Quat fromVectors(const Vector3& from, const Vector3& to);
/** Returns the axis and angle of rotation represented
by this quaternion (i.e. q = [sin(angle/2)*axis, cos(angle/2)]) */
void toAxisAngleRotation(Vector3& axis, double& angle) const;
void toAxisAngleRotation(Vector3& axis, float& angle) const
{
double d;
toAxisAngleRotation(axis, d);
angle = (float)d;
}
Matrix3 toRotationMatrix() const;
void toRotationMatrix(Matrix3& rot) const;
/**
Spherical linear interpolation: linear interpolation along the
shortest (3D) great-circle route between two quaternions.
Note: Correct rotations are expected between 0 and PI in the right order.
@cite Based on Game Physics -- David Eberly pg 538-540
@param threshold Critical angle between between rotations at which
the algorithm switches to normalized lerp, which is more
numerically stable in those situations. 0.0 will always slerp.
*/
Quat slerp(const Quat& other, float alpha, float threshold = 0.05f) const;
/** Normalized linear interpolation of quaternion components. */
Quat nlerp(const Quat& other, float alpha) const;
/** Note that q<SUP>-1</SUP> = q.conj() for a unit quaternion.
@cite Dam99 page 13 */
inline Quat inverse() const
{
return conj() / dot(*this);
}
/**
Quaternion multiplication (composition of rotations).
Note that this does not commute.
*/
Quat operator*(const Quat& other) const;
/* Quaternion-vector transformation */
Vector3 operator*(const Vector3& other) const;
/* (*this) * other.inverse() */
Quat operator/(const Quat& other) const
{
return (*this) * other.inverse();
}
/** Is the magnitude nearly 1.0? */
bool isUnit(float tolerance = 1e-5) const
{
return abs(dot(*this) - 1.0f) < tolerance;
}
float magnitude() const
{
return sqrtf(dot(*this));
}
Quat log() const
{
if ((x == 0) && (y == 0) && (z == 0))
{
if (w > 0)
{
return Quat(0, 0, 0, ::logf(w));
}
else if (w < 0)
{
// Log of a negative number. Multivalued, any number of the form
// (PI * v, ln(-q.w))
return Quat((float)pi(), 0, 0, ::logf(-w));
}
else
{
// log of zero!
return Quat((float)nan(), (float)nan(), (float)nan(), (float)nan());
}
}
else
{
// Partly imaginary.
float imagLen = sqrtf(x * x + y * y + z * z);
float len = sqrtf(imagLen * imagLen + w * w);
float theta = atan2f(imagLen, (float)w);
float t = theta / imagLen;
return Quat(t * x, t * y, t * z, ::logf(len));
}
}
/** log q = [Av, 0] where q = [sin(A) * v, cos(A)].
Only for unit quaternions
debugAssertM(isUnit(), "Log only defined for unit quaternions");
// Solve for A in q = [sin(A)*v, cos(A)]
Vector3 u(x, y, z);
double len = u.magnitude();
if (len == 0.0) {
return
}
double A = atan2((double)w, len);
Vector3 v = u / len;
return Quat(v * A, 0);
}
*/
/** exp q = [sin(A) * v, cos(A)] where q = [Av, 0].
Only defined for pure-vector quaternions */
inline Quat exp() const
{
debugAssertM(w == 0, "exp only defined for vector quaternions");
Vector3 u(x, y, z);
float A = u.magnitude();
Vector3 v = u / A;
return Quat(sinf(A) * v, cosf(A));
}
/**
Raise this quaternion to a power. For a rotation, this is
the effect of rotating x times as much as the original
quaterion.
Note that q.pow(a).pow(b) == q.pow(a + b)
@cite Dam98 pg 21
*/
inline Quat pow(float x) const
{
return (log() * x).exp();
}
/** Make unit length in place */
void unitize()
{
*this *= rsq(dot(*this));
}
/**
Returns a unit quaterion obtained by dividing through by
the magnitude.
*/
Quat toUnit() const
{
Quat x = *this;
x.unitize();
return x;
}
/**
The linear algebra 2-norm, sqrt(q dot q). This matches
the value used in Dam's 1998 tech report but differs from the
n(q) value used in Eberly's 1999 paper, which is the square of the
norm.
*/
float norm() const
{
return magnitude();
}
// access quaternion as q[0] = q.x, q[1] = q.y, q[2] = q.z, q[3] = q.w
//
// WARNING. These member functions rely on
// (1) Quat not having virtual functions
// (2) the data packed in a 4*sizeof(float) memory block
const float& operator[](int i) const;
float& operator[](int i);
/** Generate uniform random unit quaternion (i.e. random "direction")
@cite From "Uniform Random Rotations", Ken Shoemake, Graphics Gems III.
*/
static Quat unitRandom();
// 2-char swizzles
Vector2 xx() const;
Vector2 yx() const;
Vector2 zx() const;
Vector2 wx() const;
Vector2 xy() const;
Vector2 yy() const;
Vector2 zy() const;
Vector2 wy() const;
Vector2 xz() const;
Vector2 yz() const;
Vector2 zz() const;
Vector2 wz() const;
Vector2 xw() const;
Vector2 yw() const;
Vector2 zw() const;
Vector2 ww() const;
// 3-char swizzles
Vector3 xxx() const;
Vector3 yxx() const;
Vector3 zxx() const;
Vector3 wxx() const;
Vector3 xyx() const;
Vector3 yyx() const;
Vector3 zyx() const;
Vector3 wyx() const;
Vector3 xzx() const;
Vector3 yzx() const;
Vector3 zzx() const;
Vector3 wzx() const;
Vector3 xwx() const;
Vector3 ywx() const;
Vector3 zwx() const;
Vector3 wwx() const;
Vector3 xxy() const;
Vector3 yxy() const;
Vector3 zxy() const;
Vector3 wxy() const;
Vector3 xyy() const;
Vector3 yyy() const;
Vector3 zyy() const;
Vector3 wyy() const;
Vector3 xzy() const;
Vector3 yzy() const;
Vector3 zzy() const;
Vector3 wzy() const;
Vector3 xwy() const;
Vector3 ywy() const;
Vector3 zwy() const;
Vector3 wwy() const;
Vector3 xxz() const;
Vector3 yxz() const;
Vector3 zxz() const;
Vector3 wxz() const;
Vector3 xyz() const;
Vector3 yyz() const;
Vector3 zyz() const;
Vector3 wyz() const;
Vector3 xzz() const;
Vector3 yzz() const;
Vector3 zzz() const;
Vector3 wzz() const;
Vector3 xwz() const;
Vector3 ywz() const;
Vector3 zwz() const;
Vector3 wwz() const;
Vector3 xxw() const;
Vector3 yxw() const;
Vector3 zxw() const;
Vector3 wxw() const;
Vector3 xyw() const;
Vector3 yyw() const;
Vector3 zyw() const;
Vector3 wyw() const;
Vector3 xzw() const;
Vector3 yzw() const;
Vector3 zzw() const;
Vector3 wzw() const;
Vector3 xww() const;
Vector3 yww() const;
Vector3 zww() const;
Vector3 www() const;
// 4-char swizzles
Vector4 xxxx() const;
Vector4 yxxx() const;
Vector4 zxxx() const;
Vector4 wxxx() const;
Vector4 xyxx() const;
Vector4 yyxx() const;
Vector4 zyxx() const;
Vector4 wyxx() const;
Vector4 xzxx() const;
Vector4 yzxx() const;
Vector4 zzxx() const;
Vector4 wzxx() const;
Vector4 xwxx() const;
Vector4 ywxx() const;
Vector4 zwxx() const;
Vector4 wwxx() const;
Vector4 xxyx() const;
Vector4 yxyx() const;
Vector4 zxyx() const;
Vector4 wxyx() const;
Vector4 xyyx() const;
Vector4 yyyx() const;
Vector4 zyyx() const;
Vector4 wyyx() const;
Vector4 xzyx() const;
Vector4 yzyx() const;
Vector4 zzyx() const;
Vector4 wzyx() const;
Vector4 xwyx() const;
Vector4 ywyx() const;
Vector4 zwyx() const;
Vector4 wwyx() const;
Vector4 xxzx() const;
Vector4 yxzx() const;
Vector4 zxzx() const;
Vector4 wxzx() const;
Vector4 xyzx() const;
Vector4 yyzx() const;
Vector4 zyzx() const;
Vector4 wyzx() const;
Vector4 xzzx() const;
Vector4 yzzx() const;
Vector4 zzzx() const;
Vector4 wzzx() const;
Vector4 xwzx() const;
Vector4 ywzx() const;
Vector4 zwzx() const;
Vector4 wwzx() const;
Vector4 xxwx() const;
Vector4 yxwx() const;
Vector4 zxwx() const;
Vector4 wxwx() const;
Vector4 xywx() const;
Vector4 yywx() const;
Vector4 zywx() const;
Vector4 wywx() const;
Vector4 xzwx() const;
Vector4 yzwx() const;
Vector4 zzwx() const;
Vector4 wzwx() const;
Vector4 xwwx() const;
Vector4 ywwx() const;
Vector4 zwwx() const;
Vector4 wwwx() const;
Vector4 xxxy() const;
Vector4 yxxy() const;
Vector4 zxxy() const;
Vector4 wxxy() const;
Vector4 xyxy() const;
Vector4 yyxy() const;
Vector4 zyxy() const;
Vector4 wyxy() const;
Vector4 xzxy() const;
Vector4 yzxy() const;
Vector4 zzxy() const;
Vector4 wzxy() const;
Vector4 xwxy() const;
Vector4 ywxy() const;
Vector4 zwxy() const;
Vector4 wwxy() const;
Vector4 xxyy() const;
Vector4 yxyy() const;
Vector4 zxyy() const;
Vector4 wxyy() const;
Vector4 xyyy() const;
Vector4 yyyy() const;
Vector4 zyyy() const;
Vector4 wyyy() const;
Vector4 xzyy() const;
Vector4 yzyy() const;
Vector4 zzyy() const;
Vector4 wzyy() const;
Vector4 xwyy() const;
Vector4 ywyy() const;
Vector4 zwyy() const;
Vector4 wwyy() const;
Vector4 xxzy() const;
Vector4 yxzy() const;
Vector4 zxzy() const;
Vector4 wxzy() const;
Vector4 xyzy() const;
Vector4 yyzy() const;
Vector4 zyzy() const;
Vector4 wyzy() const;
Vector4 xzzy() const;
Vector4 yzzy() const;
Vector4 zzzy() const;
Vector4 wzzy() const;
Vector4 xwzy() const;
Vector4 ywzy() const;
Vector4 zwzy() const;
Vector4 wwzy() const;
Vector4 xxwy() const;
Vector4 yxwy() const;
Vector4 zxwy() const;
Vector4 wxwy() const;
Vector4 xywy() const;
Vector4 yywy() const;
Vector4 zywy() const;
Vector4 wywy() const;
Vector4 xzwy() const;
Vector4 yzwy() const;
Vector4 zzwy() const;
Vector4 wzwy() const;
Vector4 xwwy() const;
Vector4 ywwy() const;
Vector4 zwwy() const;
Vector4 wwwy() const;
Vector4 xxxz() const;
Vector4 yxxz() const;
Vector4 zxxz() const;
Vector4 wxxz() const;
Vector4 xyxz() const;
Vector4 yyxz() const;
Vector4 zyxz() const;
Vector4 wyxz() const;
Vector4 xzxz() const;
Vector4 yzxz() const;
Vector4 zzxz() const;
Vector4 wzxz() const;
Vector4 xwxz() const;
Vector4 ywxz() const;
Vector4 zwxz() const;
Vector4 wwxz() const;
Vector4 xxyz() const;
Vector4 yxyz() const;
Vector4 zxyz() const;
Vector4 wxyz() const;
Vector4 xyyz() const;
Vector4 yyyz() const;
Vector4 zyyz() const;
Vector4 wyyz() const;
Vector4 xzyz() const;
Vector4 yzyz() const;
Vector4 zzyz() const;
Vector4 wzyz() const;
Vector4 xwyz() const;
Vector4 ywyz() const;
Vector4 zwyz() const;
Vector4 wwyz() const;
Vector4 xxzz() const;
Vector4 yxzz() const;
Vector4 zxzz() const;
Vector4 wxzz() const;
Vector4 xyzz() const;
Vector4 yyzz() const;
Vector4 zyzz() const;
Vector4 wyzz() const;
Vector4 xzzz() const;
Vector4 yzzz() const;
Vector4 zzzz() const;
Vector4 wzzz() const;
Vector4 xwzz() const;
Vector4 ywzz() const;
Vector4 zwzz() const;
Vector4 wwzz() const;
Vector4 xxwz() const;
Vector4 yxwz() const;
Vector4 zxwz() const;
Vector4 wxwz() const;
Vector4 xywz() const;
Vector4 yywz() const;
Vector4 zywz() const;
Vector4 wywz() const;
Vector4 xzwz() const;
Vector4 yzwz() const;
Vector4 zzwz() const;
Vector4 wzwz() const;
Vector4 xwwz() const;
Vector4 ywwz() const;
Vector4 zwwz() const;
Vector4 wwwz() const;
Vector4 xxxw() const;
Vector4 yxxw() const;
Vector4 zxxw() const;
Vector4 wxxw() const;
Vector4 xyxw() const;
Vector4 yyxw() const;
Vector4 zyxw() const;
Vector4 wyxw() const;
Vector4 xzxw() const;
Vector4 yzxw() const;
Vector4 zzxw() const;
Vector4 wzxw() const;
Vector4 xwxw() const;
Vector4 ywxw() const;
Vector4 zwxw() const;
Vector4 wwxw() const;
Vector4 xxyw() const;
Vector4 yxyw() const;
Vector4 zxyw() const;
Vector4 wxyw() const;
Vector4 xyyw() const;
Vector4 yyyw() const;
Vector4 zyyw() const;
Vector4 wyyw() const;
Vector4 xzyw() const;
Vector4 yzyw() const;
Vector4 zzyw() const;
Vector4 wzyw() const;
Vector4 xwyw() const;
Vector4 ywyw() const;
Vector4 zwyw() const;
Vector4 wwyw() const;
Vector4 xxzw() const;
Vector4 yxzw() const;
Vector4 zxzw() const;
Vector4 wxzw() const;
Vector4 xyzw() const;
Vector4 yyzw() const;
Vector4 zyzw() const;
Vector4 wyzw() const;
Vector4 xzzw() const;
Vector4 yzzw() const;
Vector4 zzzw() const;
Vector4 wzzw() const;
Vector4 xwzw() const;
Vector4 ywzw() const;
Vector4 zwzw() const;
Vector4 wwzw() const;
Vector4 xxww() const;
Vector4 yxww() const;
Vector4 zxww() const;
Vector4 wxww() const;
Vector4 xyww() const;
Vector4 yyww() const;
Vector4 zyww() const;
Vector4 wyww() const;
Vector4 xzww() const;
Vector4 yzww() const;
Vector4 zzww() const;
Vector4 wzww() const;
Vector4 xwww() const;
Vector4 ywww() const;
Vector4 zwww() const;
Vector4 wwww() const;
};
inline Quat exp(const Quat& q)
{
return q.exp();
}
inline Quat log(const Quat& q)
{
return q.log();
}
inline G3D::Quat operator*(double s, const G3D::Quat& q)
{
return q * (float)s;
}
inline G3D::Quat operator*(float s, const G3D::Quat& q)
{
return q * s;
}
inline float& Quat::operator[](int i)
{
debugAssert(i >= 0);
debugAssert(i < 4);
return ((float*)this)[i];
}
inline const float& Quat::operator[](int i) const
{
debugAssert(i >= 0);
debugAssert(i < 4);
return ((float*)this)[i];
}
} // Namespace G3D
// Outside the namespace to avoid overloading confusion for C++
inline G3D::Quat pow(const G3D::Quat& q, double x)
{
return q.pow((float)x);
}
#endif

219
engine/3d/src/Random.cpp Normal file
View File

@@ -0,0 +1,219 @@
/**
@file Random.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2009-01-02
@edited 2009-03-29
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#include "Random.hpp"
namespace G3D
{
Random& Random::common()
{
static Random r;
return r;
}
Random::Random(void* x)
: state(NULL)
{
(void)x;
}
Random::Random(uint32 seed)
{
const uint32 X = 1812433253UL;
state = new uint32[N];
state[0] = seed;
for (index = 1; index < (int)N; ++index)
{
state[index] = X * (state[index - 1] ^ (state[index - 1] >> 30)) + index;
}
}
Random::~Random()
{
delete[] state;
state = NULL;
}
uint32 Random::bits()
{
// See http://en.wikipedia.org/wiki/Mersenne_twister
// Make a local copy of the index variable to ensure that it
// is not out of bounds
int localIndex = index;
// Automatically checks for index < 0 if corrupted
// by unsynchronized threads.
if ((unsigned int)localIndex >= (unsigned int)N)
{
generate();
localIndex = 0;
}
// Increment the global index. It may go out of bounds on
// multiple threads, but the above check ensures that the
// array index actually used never goes out of bounds.
// It doesn't matter if we grab the same array index twice
// on two threads, since the distribution of random numbers
// will still be uniform.
++index;
// Return the next random in the sequence
uint32 r = state[localIndex];
// Temper the result
r ^= r >> U;
r ^= (r << S) & B;
r ^= (r << T) & C;
r ^= r >> L;
return r;
}
/** Generate the next N ints, and store them for readback later */
void Random::generate()
{
// Lower R bits
static const uint32 LOWER_MASK = (1LU << R) - 1;
// Upper (32 - R) bits
static const uint32 UPPER_MASK = 0xFFFFFFFF << R;
static const uint32 mag01[2] = {0UL, (uint32)A};
// First N - M
for (unsigned int i = 0; i < N - M; ++i)
{
uint32 x = (state[i] & UPPER_MASK) | (state[i + 1] & LOWER_MASK);
state[i] = state[i + M] ^ (x >> 1) ^ mag01[x & 1];
}
// Rest
for (unsigned int i = N - M + 1; i < N - 1; ++i)
{
uint32 x = (state[i] & UPPER_MASK) | (state[i + 1] & LOWER_MASK);
state[i] = state[i + (M - N)] ^ (x >> 1) ^ mag01[x & 1];
}
uint32 y = (state[N - 1] & UPPER_MASK) | (state[0] & LOWER_MASK);
state[N - 1] = state[M - 1] ^ (y >> 1) ^ mag01[y & 1];
index = 0;
}
int Random::integer(int low, int high)
{
int r = iFloor(low + (high - low + 1) * (double)bits() / 0xFFFFFFFFUL);
// There is a *very small* chance of generating
// a number larger than high.
if (r > high)
{
return high;
}
else
{
return r;
}
}
float Random::gaussian(float mean, float stdev)
{
// Using Box-Mueller method from http://www.taygeta.com/random/gaussian.html
// Modified to specify standard deviation and mean of distribution
float w, x1, x2;
// Loop until w is less than 1 so that log(w) is negative
do
{
x1 = uniform(-1.0, 1.0);
x2 = uniform(-1.0, 1.0);
w = float(square(x1) + square(x2));
} while (w > 1.0f);
// Transform to gassian distribution
// Multiply by sigma (stdev ^ 2) and add mean.
return x2 * (float)square(stdev) * sqrtf((-2.0f * logf(w)) / w) + mean;
}
void Random::cosHemi(float& x, float& y, float& z)
{
const float e1 = uniform();
const float e2 = uniform();
// Jensen's method
const float sin_theta = sqrtf(1.0f - e1);
const float cos_theta = sqrtf(e1);
const float phi = 6.28318531f * e2;
x = cos(phi) * sin_theta;
y = sin(phi) * sin_theta;
z = cos_theta;
// We could also use Malley's method (pbrt p.657), since they are the same cost:
//
// r = sqrt(e1);
// t = 2*pi*e2;
// x = cos(t)*r;
// y = sin(t)*r;
// z = sqrt(1.0 - x*x + y*y);
}
void Random::cosPowHemi(const float k, float& x, float& y, float& z)
{
const float e1 = uniform();
const float e2 = uniform();
const float cos_theta = pow(e1, 1.0f / (k + 1.0f));
const float sin_theta = sqrtf(1.0f - square(cos_theta));
const float phi = 6.28318531f * e2;
x = cos(phi) * sin_theta;
y = sin(phi) * sin_theta;
z = cos_theta;
}
void Random::hemi(float& x, float& y, float& z)
{
sphere(x, y, z);
z = fabsf(z);
}
void Random::sphere(float& x, float& y, float& z)
{
// Squared magnitude
float m2;
// Rejection sample
do
{
x = uniform() * 2.0f - 1.0f, y = uniform() * 2.0f - 1.0f, z = uniform() * 2.0f - 1.0f;
m2 = x * x + y * y + z * z;
} while (m2 >= 1.0f);
// Divide by magnitude to produce a unit vector
float s = rsqrt(m2);
x *= s;
y *= s;
z *= s;
}
} // namespace G3D

132
engine/3d/src/Random.hpp Normal file
View File

@@ -0,0 +1,132 @@
/**
@file Random.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2009-01-02
@edited 2009-03-20
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_Random_h
#define G3D_Random_h
#include "platform.hpp"
#include "g3dmath.hpp"
namespace G3D
{
/** Random number generator.
Threadsafe.
Useful for generating consistent random numbers across platforms
and when multiple threads are involved.
Uses the Fast Mersenne Twister (FMT-19937) algorithm.
On average, uniform() runs about 2x-3x faster than rand().
@cite http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html
On OS X, Random is about 10x faster than drand48()and 4x faster than rand()
Not thread safe anymore, but who cares
*/
class Random
{
protected:
/** Constants (important for the algorithm; do not modify) */
enum
{
N = 624,
M = 397,
R = 31,
U = 11,
S = 7,
T = 15,
L = 18,
A = 0x9908B0DF,
B = 0x9D2C5680,
C = 0xEFC60000
};
/** State vector (these are the next N values that will be returned) */
uint32* state;
/** Index into state */
int index;
/** Generate the next N ints, and store them for readback later.
Called from bits() */
virtual void generate();
/** For subclasses. The void* parameter is just to distinguish this from the
public constructor.*/
Random(void*);
public:
Random(uint32 seed = 0xF018A4D2);
virtual ~Random();
/** Each bit is random. Subclasses can choose to override just
this method and the other methods will all work automatically. */
virtual uint32 bits();
/** Uniform random integer on the range [min, max] */
virtual int integer(int min, int max);
/** Uniform random float on the range [min, max] */
virtual inline float uniform(float low, float high)
{
// We could compute the ratio in double precision here for
// about 1.5x slower performance and slightly better
// precision.
return low + (high - low) * ((float)bits() / (float)0xFFFFFFFFUL);
}
/** Uniform random float on the range [0, 1] */
virtual inline float uniform()
{
// We could compute the ratio in double precision here for
// about 1.5x slower performance and slightly better
// precision.
const float norm = 1.0f / (float)0xFFFFFFFFUL;
return (float)bits() * norm;
}
/** Normally distributed reals. */
virtual float gaussian(float mean, float stdev);
/** Returns 3D unit vectors distributed according to
a cosine distribution about the z-axis. */
virtual void cosHemi(float& x, float& y, float& z);
/** Returns 3D unit vectors distributed according to a cosine
power distribution (\f$ \cos^k \theta \f$) about
the z-axis. */
virtual void cosPowHemi(const float k, float& x, float& y, float& z);
/** Returns 3D unit vectors uniformly distributed on the
hemisphere about the z-axis. */
virtual void hemi(float& x, float& y, float& z);
/** Returns 3D unit vectors uniformly distributed on the sphere */
virtual void sphere(float& x, float& y, float& z);
/**
A shared instance for when the performance and features but not
consistency of the class are desired. It is slightly (10%)
faster to use a distinct instance than to use the common one.
Threadsafe.
*/
static Random& common();
};
} // namespace G3D
#endif

274
engine/3d/src/Ray.cpp Normal file
View File

@@ -0,0 +1,274 @@
/**
Aya - We are not using the G3D 8.0 G3D::Ray, intstead we are using the Aya:RbxRay from old G3D.
@file Ray.cpp
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-07-12
@edited 2004-03-19
*/
#include "platform.hpp"
#include "Ray.hpp"
#include "Plane.hpp"
#include "Sphere.hpp"
#include "CollisionDetection.hpp"
namespace G3D
{
void Ray::set(const Vector3& origin, const Vector3& direction)
{
m_origin = origin;
m_direction = direction;
debugAssert(direction.isUnit());
m_invDirection = Vector3::one() / direction;
// ray slope
ibyj = m_direction.x * m_invDirection.y;
jbyi = m_direction.y * m_invDirection.x;
jbyk = m_direction.y * m_invDirection.z;
kbyj = m_direction.z * m_invDirection.y;
ibyk = m_direction.x * m_invDirection.z;
kbyi = m_direction.z * m_invDirection.x;
// precomputed terms
c_xy = m_origin.y - jbyi * m_origin.x;
c_xz = m_origin.z - kbyi * m_origin.x;
c_yx = m_origin.x - ibyj * m_origin.y;
c_yz = m_origin.z - kbyj * m_origin.y;
c_zx = m_origin.x - ibyk * m_origin.z;
c_zy = m_origin.y - jbyk * m_origin.z;
// ray slope classification
if (m_direction.x < 0)
{
if (m_direction.y < 0)
{
if (m_direction.z < 0)
{
classification = MMM;
}
else if (m_direction.z > 0)
{
classification = MMP;
}
else
{ //(m_direction.z >= 0)
classification = MMO;
}
}
else
{ //(m_direction.y >= 0)
if (m_direction.z < 0)
{
if (m_direction.y == 0)
{
classification = MOM;
}
else
{
classification = MPM;
}
}
else
{ //(m_direction.z >= 0)
if ((m_direction.y == 0) && (m_direction.z == 0))
{
classification = MOO;
}
else if (m_direction.z == 0)
{
classification = MPO;
}
else if (m_direction.y == 0)
{
classification = MOP;
}
else
{
classification = MPP;
}
}
}
}
else
{ //(m_direction.x >= 0)
if (m_direction.y < 0)
{
if (m_direction.z < 0)
{
if (m_direction.x == 0)
{
classification = OMM;
}
else
{
classification = PMM;
}
}
else
{ //(m_direction.z >= 0)
if ((m_direction.x == 0) && (m_direction.z == 0))
{
classification = OMO;
}
else if (m_direction.z == 0)
{
classification = PMO;
}
else if (m_direction.x == 0)
{
classification = OMP;
}
else
{
classification = PMP;
}
}
}
else
{ //(m_direction.y >= 0)
if (m_direction.z < 0)
{
if ((m_direction.x == 0) && (m_direction.y == 0))
{
classification = OOM;
}
else if (m_direction.x == 0)
{
classification = OPM;
}
else if (m_direction.y == 0)
{
classification = POM;
}
else
{
classification = PPM;
}
}
else
{ //(m_direction.z > 0)
if (m_direction.x == 0)
{
if (m_direction.y == 0)
{
classification = OOP;
}
else if (m_direction.z == 0)
{
classification = OPO;
}
else
{
classification = OPP;
}
}
else
{
if ((m_direction.y == 0) && (m_direction.z == 0))
{
classification = POO;
}
else if (m_direction.y == 0)
{
classification = POP;
}
else if (m_direction.z == 0)
{
classification = PPO;
}
else
{
classification = PPP;
}
}
}
}
}
}
Ray Ray::refract(const Vector3& newOrigin, const Vector3& normal, float iInside, float iOutside) const
{
Vector3 D = m_direction.refractionDirection(normal, iInside, iOutside);
return Ray(newOrigin + (m_direction + normal * (float)sign(m_direction.dot(normal))) * 0.001f, D);
}
Ray Ray::reflect(const Vector3& newOrigin, const Vector3& normal) const
{
Vector3 D = m_direction.reflectionDirection(normal);
return Ray(newOrigin + (D + normal) * 0.001f, D);
}
Vector3 Ray::intersection(const Plane& plane) const
{
float d;
Vector3 normal = plane.normal();
plane.getEquation(normal, d);
float rate = m_direction.dot(normal);
if (rate >= 0.0f)
{
return Vector3::inf();
}
else
{
float t = -(d + m_origin.dot(normal)) / rate;
return m_origin + m_direction * t;
}
}
float Ray::intersectionTime(const class Sphere& sphere, bool solid) const
{
Vector3 dummy;
return CollisionDetection::collisionTimeForMovingPointFixedSphere(m_origin, m_direction, sphere, dummy, dummy, solid);
}
float Ray::intersectionTime(const class Plane& plane) const
{
Vector3 dummy;
return CollisionDetection::collisionTimeForMovingPointFixedPlane(m_origin, m_direction, plane, dummy);
}
float Ray::intersectionTime(const class Box& box) const
{
Vector3 dummy;
float time = CollisionDetection::collisionTimeForMovingPointFixedBox(m_origin, m_direction, box, dummy);
if ((time == finf()) && (box.contains(m_origin)))
{
return 0.0f;
}
else
{
return time;
}
}
float Ray::intersectionTime(const class AABox& box) const
{
Vector3 dummy;
bool inside;
float time = CollisionDetection::collisionTimeForMovingPointFixedAABox(m_origin, m_direction, box, dummy, inside);
if ((time == finf()) && inside)
{
return 0.0f;
}
else
{
return time;
}
}
} // namespace G3D

390
engine/3d/src/Ray.hpp Normal file
View File

@@ -0,0 +1,390 @@
/**
Aya - We are not using the G3D 8.0 G3D::Ray, intstead we are using the Aya:RbxRay from old G3D.
@file Ray.h
Ray class
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-07-12
@edited 2009-06-29
*/
#ifndef G3D_Ray_h
#define G3D_Ray_h
#include "platform.hpp"
#include "Vector3.hpp"
#include "Triangle.hpp"
namespace G3D
{
/**
A 3D Ray.
*/
class Ray
{
private:
friend class Intersect;
Vector3 m_origin;
/** Unit length */
Vector3 m_direction;
/** 1.0 / direction */
Vector3 m_invDirection;
// The following are for the "ray slope" optimization from
// "Fast Ray / Axis-Aligned Bounding Box Overlap Tests using Ray Slopes"
// by Martin Eisemann, Thorsten Grosch, Stefan M<>ller and Marcus Magnor
// Computer Graphics Lab, TU Braunschweig, Germany and
// University of Koblenz-Landau, Germany*/
enum Classification
{
MMM,
MMP,
MPM,
MPP,
PMM,
PMP,
PPM,
PPP,
POO,
MOO,
OPO,
OMO,
OOP,
OOM,
OMM,
OMP,
OPM,
OPP,
MOM,
MOP,
POM,
POP,
MMO,
MPO,
PMO,
PPO
};
Classification classification;
// ray slope
float ibyj, jbyi, kbyj, jbyk, ibyk, kbyi;
// Precomputed components
float c_xy, c_xz, c_yx, c_yz, c_zx, c_zy;
public:
void set(const Vector3& origin, const Vector3& direction);
inline const Vector3& origin() const
{
return m_origin;
}
/** Unit direction vector. */
inline const Vector3& direction() const
{
return m_direction;
}
/** Component-wise inverse of direction vector. May have inf() components */
inline const Vector3& invDirection() const
{
return m_invDirection;
}
inline Ray()
{
set(Vector3::zero(), Vector3::unitX());
}
inline Ray(const Vector3& origin, const Vector3& direction)
{
set(origin, direction);
}
/**
Creates a Ray from a origin and a (nonzero) unit direction.
*/
static Ray fromOriginAndDirection(const Vector3& point, const Vector3& direction)
{
debugAssert(1); /// Aya - Do not use, intead use Aya::RbxRay
return Ray(point, direction);
}
/** Advances the origin along the direction by @a distance */
inline Ray bump(float distance) const
{
return Ray(m_origin + m_direction * distance, m_direction);
}
/** Advances the origin along the @a bumpDirection by @a distance and returns the new ray*/
inline Ray bump(float distance, const Vector3& bumpDirection) const
{
return Ray(m_origin + bumpDirection * distance, m_direction);
}
/**
Returns the closest point on the Ray to point.
*/
Vector3 closestPoint(const Vector3& point) const
{
float t = m_direction.dot(point - m_origin);
if (t < 0)
{
return m_origin;
}
else
{
return m_origin + m_direction * t;
}
}
/**
Returns the closest distance between point and the Ray
*/
float distance(const Vector3& point) const
{
return (closestPoint(point) - point).magnitude();
}
/**
Returns the point where the Ray and plane intersect. If there
is no intersection, returns a point at infinity.
Planes are considered one-sided, so the ray will not intersect
a plane where the normal faces in the traveling direction.
*/
Vector3 intersection(const class Plane& plane) const;
/**
Returns the distance until intersection with the sphere or the (solid) ball bounded by the sphere.
Will be 0 if inside the sphere, inf if there is no intersection.
The ray direction is <B>not</B> normalized. If the ray direction
has unit length, the distance from the origin to intersection
is equal to the time. If the direction does not have unit length,
the distance = time * direction.length().
See also the G3D::CollisionDetection "movingPoint" methods,
which give more information about the intersection.
\param solid If true, rays inside the sphere immediately intersect (good for collision detection). If false, they hit the opposite side of the
sphere (good for ray tracing).
*/
float intersectionTime(const class Sphere& sphere, bool solid = false) const;
float intersectionTime(const class Plane& plane) const;
float intersectionTime(const class Box& box) const;
float intersectionTime(const class AABox& box) const;
/**
The three extra arguments are the weights of vertices 0, 1, and 2
at the intersection point; they are useful for texture mapping
and interpolated normals.
*/
float intersectionTime(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Vector3& edge01, const Vector3& edge02, double& w0,
double& w1, double& w2) const;
/**
Ray-triangle intersection for a 1-sided triangle. Fastest version.
@cite http://www.acm.org/jgt/papers/MollerTrumbore97/
http://www.graphics.cornell.edu/pubs/1997/MT97.html
*/
inline float intersectionTime(
const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, const Vector3& edge01, const Vector3& edge02) const;
inline float intersectionTime(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2) const
{
return intersectionTime(vert0, vert1, vert2, vert1 - vert0, vert2 - vert0);
}
inline float intersectionTime(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, double& w0, double& w1, double& w2) const
{
return intersectionTime(vert0, vert1, vert2, vert1 - vert0, vert2 - vert0, w0, w1, w2);
}
/* One-sided triangle
*/
inline float intersectionTime(const Triangle& triangle) const
{
return intersectionTime(triangle.vertex(0), triangle.vertex(1), triangle.vertex(2), triangle.edge01(), triangle.edge02());
}
inline float intersectionTime(const Triangle& triangle, double& w0, double& w1, double& w2) const
{
return intersectionTime(triangle.vertex(0), triangle.vertex(1), triangle.vertex(2), triangle.edge01(), triangle.edge02(), w0, w1, w2);
}
/** Refracts about the normal
using G3D::Vector3::refractionDirection
and bumps the ray slightly from the newOrigin. */
Ray refract(const Vector3& newOrigin, const Vector3& normal, float iInside, float iOutside) const;
/** Reflects about the normal
using G3D::Vector3::reflectionDirection
and bumps the ray slightly from
the newOrigin. */
Ray reflect(const Vector3& newOrigin, const Vector3& normal) const;
};
#define EPSILON 0.000001
#define CROSS(dest, v1, v2) \
dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; \
dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; \
dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
#define DOT(v1, v2) (v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2])
#define SUB(dest, v1, v2) \
dest[0] = v1[0] - v2[0]; \
dest[1] = v1[1] - v2[1]; \
dest[2] = v1[2] - v2[2];
inline float Ray::intersectionTime(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, const Vector3& edge1, const Vector3& edge2) const
{
(void)vert1;
(void)vert2;
// Barycenteric coords
float u, v;
float tvec[3], pvec[3], qvec[3];
// begin calculating determinant - also used to calculate U parameter
CROSS(pvec, m_direction, edge2);
// if determinant is near zero, ray lies in plane of triangle
const float det = DOT(edge1, pvec);
if (det < EPSILON)
{
return finf();
}
// calculate distance from vert0 to ray origin
SUB(tvec, m_origin, vert0);
// calculate U parameter and test bounds
u = DOT(tvec, pvec);
if ((u < 0.0f) || (u > det))
{
// Hit the plane outside the triangle
return finf();
}
// prepare to test V parameter
CROSS(qvec, tvec, edge1);
// calculate V parameter and test bounds
v = DOT(m_direction, qvec);
if ((v < 0.0f) || (u + v > det))
{
// Hit the plane outside the triangle
return finf();
}
// Case where we don't need correct (u, v):
const float t = DOT(edge2, qvec);
if (t >= 0.0f)
{
// Note that det must be positive
return t / det;
}
else
{
// We had to travel backwards in time to intersect
return finf();
}
}
inline float Ray::intersectionTime(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, const Vector3& edge1, const Vector3& edge2,
double& w0, double& w1, double& w2) const
{
(void)vert1;
(void)vert2;
// Barycenteric coords
float u, v;
float tvec[3], pvec[3], qvec[3];
// begin calculating determinant - also used to calculate U parameter
CROSS(pvec, m_direction, edge2);
// if determinant is near zero, ray lies in plane of triangle
const float det = DOT(edge1, pvec);
if (det < EPSILON)
{
return finf();
}
// calculate distance from vert0 to ray origin
SUB(tvec, m_origin, vert0);
// calculate U parameter and test bounds
u = DOT(tvec, pvec);
if ((u < 0.0f) || (u > det))
{
// Hit the plane outside the triangle
return finf();
}
// prepare to test V parameter
CROSS(qvec, tvec, edge1);
// calculate V parameter and test bounds
v = DOT(m_direction, qvec);
if ((v < 0.0f) || (u + v > det))
{
// Hit the plane outside the triangle
return finf();
}
float t = DOT(edge2, qvec);
if (t >= 0)
{
const float inv_det = 1.0f / det;
t *= inv_det;
u *= inv_det;
v *= inv_det;
w0 = (1.0f - u - v);
w1 = u;
w2 = v;
return t;
}
else
{
// We had to travel backwards in time to intersect
return finf();
}
}
#undef EPSILON
#undef CROSS
#undef DOT
#undef SUB
} // namespace G3D
#endif

489
engine/3d/src/RbxCamera.cpp Normal file
View File

@@ -0,0 +1,489 @@
/**
Roblox
This is not the G3D 8.0 GCamera, instead it is from the old G3D release modified by Roblox.
@file RbxCamera.cpp
@author Morgan McGuire, matrix@graphics3d.com
@created 2001-04-15
@edited 2006-01-11
*/
#include "RbxCamera.hpp"
#include "platform.hpp"
#include "Ray.hpp"
#include "Matrix4.hpp"
namespace Aya
{
RbxCamera::RbxCamera()
{
nearPlane = 0.1f;
farPlane = (float)inf();
setFieldOfView((float)G3D::toRadians(55.0f));
}
RbxCamera::~RbxCamera() {}
CoordinateFrame RbxCamera::coordinateFrame() const
{
return cframe;
}
void RbxCamera::getCoordinateFrame(CoordinateFrame& c) const
{
c = cframe;
}
void RbxCamera::setCoordinateFrame(const CoordinateFrame& c)
{
cframe = c;
}
void RbxCamera::setFieldOfView(float angle)
{
debugAssert((angle < G3D::pi()) && (angle > 0));
fieldOfView = angle;
// Solve for the corresponding image plane depth, as if the extent
// of the film was 1x1.
imagePlaneDepth = 1.0f / (2.0f * tanf(angle / 2.0f));
}
void RbxCamera::setImagePlaneDepth(float depth, const class G3D::Rect2D& viewport)
{
debugAssert(depth > 0);
setFieldOfView(2.0f * atanf(viewport.height() / (2.0f * depth)));
}
float RbxCamera::getImagePlaneDepth(const class G3D::Rect2D& viewport) const
{
// The image plane depth has been pre-computed for
// a 1x1 image. Now that the image is width x height,
// we need to scale appropriately.
return imagePlaneDepth * viewport.height();
}
float RbxCamera::getViewportWidth(const G3D::Rect2D& viewport) const
{
return getViewportHeight(viewport) * viewport.width() / viewport.height();
}
float RbxCamera::getViewportHeight(const G3D::Rect2D& viewport) const
{
(void)viewport;
return nearPlane / imagePlaneDepth;
}
// Aya
Aya::RbxRay RbxCamera::worldRay(float x, float y, const G3D::Rect2D& viewport) const
{
int screenWidth = G3D::iFloor(viewport.width());
int screenHeight = G3D::iFloor(viewport.height());
Vector3 origin = cframe.translation;
float cx = screenWidth / 2.0f;
float cy = screenHeight / 2.0f;
Vector3 direction = Vector3((x - cx), -(y - cy), -(getImagePlaneDepth(viewport)));
direction = cframe.vectorToWorldSpace(direction);
// Normalize the direction (we didn't do it before)
direction = direction.direction();
return Aya::RbxRay::fromOriginAndDirection(origin, direction);
}
// ==================
Vector3 RbxCamera::project(const Vector3& point, const G3D::Rect2D& viewport) const
{
int screenWidth = (int)viewport.width();
int screenHeight = (int)viewport.height();
Vector3 out = cframe.pointToObjectSpace(point);
float w = out.z * (-1.0f);
if (w <= 0)
{
// provide at least basic quadrant information.
// (helps with clipping)
return Vector3(((out.x < 0) ? -std::numeric_limits<float>::infinity() : std::numeric_limits<float>::infinity()),
((out.y > 0) ? -std::numeric_limits<float>::infinity() : std::numeric_limits<float>::infinity()), std::numeric_limits<float>::infinity());
}
debugAssert(w > 0);
// Find where it hits an image plane of these dimensions
float zImagePlane = getImagePlaneDepth(viewport);
// Recover the distance
float rhw = zImagePlane / w;
// Add the image center, flip the y axis
out.x = screenWidth / 2.0f - (rhw * out.x * (-1.0f));
out.y = screenHeight / 2.0f - (rhw * out.y);
out.z = rhw;
return out;
}
Vector3 RbxCamera::inverseProject(const Vector3& point, const G3D::Rect2D& viewport) const
{
int screenWidth = (int)viewport.width();
int screenHeight = (int)viewport.height();
float rhw = point.z;
Vector3 p;
// back to [-1..1][-1..1] coords ?
p.x = (screenWidth / 2.0f - point.x) / (rhw * (-1.0f));
p.y = (screenHeight / 2.0f - point.y) / (rhw);
float zImagePlane = getImagePlaneDepth(viewport);
float w = zImagePlane / rhw;
p.z = w * (-1.0f);
Vector3 out = cframe.pointToWorldSpace(p);
return out;
}
Matrix4 RbxCamera::projectionMatrix(const class G3D::Rect2D& viewport) const
{
double pixelAspect = viewport.width() / viewport.height();
// Half extents of viewport
double y = -nearPlaneZ() * tan(getFieldOfView() / 2.0);
double x = y * pixelAspect;
double r, l, t, b, n, f;
n = -nearPlaneZ();
f = -farPlaneZ();
r = x;
l = -x;
t = y;
b = -y;
return Matrix4::perspectiveProjection(l, r, b, t, n, f);
}
float RbxCamera::worldToScreenSpaceArea(float area, float z, const G3D::Rect2D& viewport) const
{
if (z >= 0)
{
return (float)inf();
}
float zImagePlane = getImagePlaneDepth(viewport);
return area * (float)G3D::square(zImagePlane / z);
}
/*
double RbxCamera::getZValue(
double x,
double y,
const class G3D::Rect2D& viewport int width,
int height,
double lineOffset) const {
double depth = renderDevice->getDepthBufferValue((int)x, (int)(height - y));
double n = -nearPlane;
double f = -farPlane;
// Undo the hyperbolic scaling.
// Derivation:
// a = ((1/out) - (1/n)) / ((1/f) - (1/n))
// depth = (1-a) * lineOffset) + (a * 1)
//
// depth = lineOffset + a * (-lineOffset + 1)
// depth = lineOffset + (((1/z) - (1/n)) / ((1/f) - (1/n))) * (1 - lineOffset)
// depth - lineOffset = (((1/z) - (1/n)) / ((1/f) - (1/n))) * (1 - lineOffset)
//
//(depth - lineOffset) / (1 - lineOffset) = (((1/z) - (1/n)) / ((1/f) - (1/n)))
//((1/f) - (1/n)) * (depth - lineOffset) / (1 - lineOffset) = ((1/z) - (1/n))
//(((1/f) - (1/n)) * (depth - lineOffset) / (1 - lineOffset)) + 1/n = (1/z)
//
// z = 1/( (((1/f) - (1/n)) * (depth - lineOffset) / (1 - lineOffset)) + 1/n)
if (f >= inf) {
// Infinite far plane
return 1 / (((-1/n) * (depth - lineOffset) / (1 - lineOffset)) + 1/n);
} else {
return 1 / ((((1/f) - (1/n)) * (depth - lineOffset) / (1 - lineOffset)) + 1/n);
}
}
*/
void RbxCamera::getClipPlanes(const G3D::Rect2D& viewport, Array<Plane>& clip) const
{
Frustum fr;
frustum(viewport, fr);
clip.resize(fr.faceArray.size(), G3D::DONT_SHRINK_UNDERLYING_ARRAY);
for (int f = 0; f < clip.size(); ++f)
{
clip[f] = fr.faceArray[f].plane;
}
/*
clip.resize(0, DONT_SHRINK_UNDERLYING_ARRAY);
double screenWidth = viewport.width();
double screenHeight = viewport.height();
// First construct the planes. Do this in the order of near, left,
// right, bottom, top, far so that early out clipping tests are likely
// to end quickly.
double fovx = screenWidth * fieldOfView / screenHeight;
// Near (recall that nearPlane, farPlane are positive numbers, so
// we need to negate them to produce actual z values.)
clip.append(Plane(Vector3(0,0,-1), Vector3(0,0,-nearPlane)));
// Right
clip.append(Plane(Vector3(-cos(fovx/2), 0, -sin(fovx/2)), Vector3::zero()));
// Left
clip.append(Plane(Vector3(-clip.last().normal().x, 0, clip.last().normal().z), Vector3::zero()));
// Top
clip.append(Plane(Vector3(0, -cos(fieldOfView/2), -sin(fieldOfView/2)), Vector3::zero()));
// Bottom
clip.append(Plane(Vector3(0, -clip.last().normal().y, clip.last().normal().z), Vector3::zero()));
// Far
if (farPlane < inf()) {
clip.append(Plane(Vector3(0, 0, 1), Vector3(0, 0, -farPlane)));
}
// Now transform the planes to world space
for (int p = 0; p < clip.size(); ++p) {
// Since there is no scale factor, we don't have to
// worry about the inverse transpose of the normal.
Vector3 normal;
float d;
clip[p].getEquation(normal, d);
Vector3 newNormal = cframe.rotation * normal;
if (isFinite(d)) {
d = (newNormal * -d + cframe.translation).dot(newNormal);
clip[p] = Plane(newNormal, newNormal * d);
} else {
// When d is infinite, we can't multiply 0's by it without
// generating NaNs.
clip[p] = Plane::fromEquation(newNormal.x, newNormal.y, newNormal.z, d);
}
}
*/
}
RbxCamera::Frustum RbxCamera::frustum(const G3D::Rect2D& viewport) const
{
Frustum f;
frustum(viewport, f);
return f;
}
void RbxCamera::frustum(const G3D::Rect2D& viewport, Frustum& fr) const
{
fr.vertexPos.fastClear();
fr.faceArray.fastClear();
// The volume is the convex hull of the vertices definining the view
// frustum and the light source point at infinity.
const float x = getViewportWidth(viewport) / 2;
const float y = getViewportHeight(viewport) / 2;
const float z = nearPlaneZ();
const float w = z / farPlaneZ();
const float fovx = x * fieldOfView / y;
// Near face (ccw from UR)
fr.vertexPos.append(Vector4(x, y, z, 1), Vector4(-x, y, z, 1), Vector4(-x, -y, z, 1), Vector4(x, -y, z, 1));
// Far face (ccw from UR, from origin)
fr.vertexPos.append(Vector4(x, y, z, w), Vector4(-x, y, z, w), Vector4(-x, -y, z, w), Vector4(x, -y, z, w));
Frustum::Face face;
// Near plane (wind backwards so normal faces into frustum)
// Recall that nearPlane, farPlane are positive numbers, so
// we need to negate them to produce actual z values.
face.plane = Plane(Vector3(0, 0, -1), Vector3(0, 0, -nearPlane));
face.vertexIndex[0] = 3;
face.vertexIndex[1] = 2;
face.vertexIndex[2] = 1;
face.vertexIndex[3] = 0;
fr.faceArray.append(face);
// Right plane
face.plane = Plane(Vector3(-cosf(fovx / 2), 0, -sinf(fovx / 2)), Vector3::zero());
face.vertexIndex[0] = 0;
face.vertexIndex[1] = 4;
face.vertexIndex[2] = 7;
face.vertexIndex[3] = 3;
fr.faceArray.append(face);
// Left plane
face.plane = Plane(Vector3(-fr.faceArray.last().plane.normal().x, 0, fr.faceArray.last().plane.normal().z), Vector3::zero());
face.vertexIndex[0] = 5;
face.vertexIndex[1] = 1;
face.vertexIndex[2] = 2;
face.vertexIndex[3] = 6;
fr.faceArray.append(face);
// Top plane
face.plane = Plane(Vector3(0, -cosf(fieldOfView / 2.0f), -sinf(fieldOfView / 2.0f)), Vector3::zero());
face.vertexIndex[0] = 1;
face.vertexIndex[1] = 5;
face.vertexIndex[2] = 4;
face.vertexIndex[3] = 0;
fr.faceArray.append(face);
// Bottom plane
face.plane = Plane(Vector3(0, -fr.faceArray.last().plane.normal().y, fr.faceArray.last().plane.normal().z), Vector3::zero());
face.vertexIndex[0] = 2;
face.vertexIndex[1] = 3;
face.vertexIndex[2] = 7;
face.vertexIndex[3] = 6;
fr.faceArray.append(face);
// Far plane
if (farPlane < inf())
{
face.plane = Plane(Vector3(0, 0, 1), Vector3(0, 0, -farPlane));
face.vertexIndex[0] = 4;
face.vertexIndex[1] = 5;
face.vertexIndex[2] = 6;
face.vertexIndex[3] = 7;
fr.faceArray.append(face);
}
// Transform vertices to world space
for (int v = 0; v < fr.vertexPos.size(); ++v)
{
fr.vertexPos[v] = cframe.toWorldSpace(fr.vertexPos[v]);
}
// Transform planes to world space
for (int p = 0; p < fr.faceArray.size(); ++p)
{
// Since there is no scale factor, we don't have to
// worry about the inverse transpose of the normal.
Vector3 normal;
float d;
fr.faceArray[p].plane.getEquation(normal, d);
Vector3 newNormal = cframe.rotation * normal;
if (G3D::isFinite(d))
{
d = (newNormal * -d + cframe.translation).dot(newNormal);
fr.faceArray[p].plane = Plane(newNormal, newNormal * d);
}
else
{
// When d is infinite, we can't multiply 0's by it without
// generating NaNs.
fr.faceArray[p].plane = Plane::fromEquation(newNormal.x, newNormal.y, newNormal.z, d);
}
}
}
bool RbxCamera::Frustum::containsPoint(const Vector3& point) const
{
for (int i = 0; i < 6; ++i)
{ // ignore front, back
const G3D::Plane& plane = faceArray[i].plane;
if (!plane.halfSpaceContains(point))
{
return false;
}
}
return true;
}
bool RbxCamera::Frustum::intersectsSphere(const Vector3& center, float radius) const
{
for (int i = 0; i < 6; ++i)
{ // ignore front, back
const G3D::Plane& p = faceArray[i].plane;
G3D::Plane offsetplane(p.normal(), p.distance() - radius);
if (!offsetplane.halfSpaceContains(center))
{
return false;
}
}
return true;
}
void RbxCamera::get3DViewportCorners(const G3D::Rect2D& viewport, Vector3& outUR, Vector3& outUL, Vector3& outLL, Vector3& outLR) const
{
// Must be kept in sync with frustum()
const float sign = (-1.0f);
const float w = -sign * getViewportWidth(viewport) / 2.0f;
const float h = getViewportHeight(viewport) / 2.0f;
const float z = -sign * nearPlaneZ();
// Compute the points
outUR = Vector3(w, h, z);
outUL = Vector3(-w, h, z);
outLL = Vector3(-w, -h, z);
outLR = Vector3(w, -h, z);
// Take to world space
outUR = cframe.pointToWorldSpace(outUR);
outUL = cframe.pointToWorldSpace(outUL);
outLR = cframe.pointToWorldSpace(outLR);
outLL = cframe.pointToWorldSpace(outLL);
}
void RbxCamera::setPosition(const Vector3& t)
{
cframe.translation = t;
}
void RbxCamera::lookAt(const Vector3& position, const Vector3& up)
{
cframe.lookAt(position, up);
}
} // namespace Aya

258
engine/3d/src/RbxCamera.hpp Normal file
View File

@@ -0,0 +1,258 @@
/**
Roblox
This is not the G3D 8.0 GCamera, instead it is from the old G3D release modified by Roblox.
@file RbxCamera.h
@maintainer Morgan McGuire, matrix@graphics3d.com
@created 2001-06-02
@edited 2006-02-11
*/
#ifndef G3D_AYA_CAMERA_H
#define G3D_AYA_CAMERA_H
#include "platform.hpp"
#include "CoordinateFrame.hpp"
#include "Vector3.hpp"
#include "Plane.hpp"
#include "Rect2D.hpp"
#include "debugAssert.hpp"
namespace Aya
{
using G3D::Array;
using G3D::CoordinateFrame;
using G3D::Matrix4;
using G3D::Vector3;
using G3D::Vector4;
/**
There is a viewport of width x height size in world space that corresponds to
a screenWidth x screenHeight pixel grid on a
renderDevice->getWidth() x renderDevice->getHeight()
window.
All viewport arguments are the pixel bounds of the viewport-- e.g.,
RenderDevice::getViewport().
*/
class RbxCamera
{
private:
/**
Vertical field of view (in radians)
*/
float fieldOfView;
/**
The image plane depth corresponding to a vertical field of
view, where the film size is 1x1.
*/
float imagePlaneDepth;
/**
Clipping plane, *not* imaging plane. Positive numbers.
*/
float nearPlane;
/**
Positive
*/
float farPlane;
CoordinateFrame cframe;
public:
class Frustum
{
public:
class Face
{
public:
/** Counter clockwise indices into vertexPos */
int vertexIndex[4];
/** The plane containing the face. */
Plane plane;
};
/** The vertices, in homogeneous space. If w == 0,
a vertex is at infinity. */
Array<G3D::Vector4> vertexPos;
/** The faces in the frustum. When the
far plane is at infinity, there are 5 faces,
otherwise there are 6. The faces are in the order
N,R,L,B,T,[F].
*/
Array<Face> faceArray;
bool containsPoint(const Vector3& point) const;
bool intersectsSphere(const Vector3& center, float radius) const;
};
RbxCamera();
virtual ~RbxCamera();
CoordinateFrame coordinateFrame() const;
void getCoordinateFrame(CoordinateFrame& c) const;
void setCoordinateFrame(const CoordinateFrame& c);
/**
Sets the horizontal field of view, in radians. The
initial angle is toRadians(55).
<UL>
<LI> toRadians(50) - Telephoto
<LI> toRadians(110) - Normal
<LI> toRadians(140) - Wide angle
</UL>
*/
void setFieldOfView(float angle);
/**
Sets the field of view based on a desired image plane depth
(<I>s'</I>) and film dimensions in world space. Depth must be positive. Width,
depth, and height are measured in the same units (meters are
recommended). The field of view will span the diagonal to the
image.<P> <I>Note</I>: to simulate a 35mm RbxCamera, set width =
0.36 mm and height = 0.24 mm. The width and height used are
generally not the pixel dimensions of the image.
*/
void setImagePlaneDepth(float depth, const class G3D::Rect2D& viewport);
inline double getFieldOfView() const
{
return fieldOfView;
}
/**
Projects a world space point onto a width x height screen. The
returned coordinate uses pixmap addressing: x = right and y =
down. The resulting z value is <I>rhw</I>.
If the point is behind the camera, Vector3::inf() is returned.
*/
Vector3 project(const G3D::Vector3& point, const class G3D::Rect2D& viewport) const;
Vector3 inverseProject(const Vector3& point, const G3D::Rect2D& viewport) const;
Matrix4 projectionMatrix(const class G3D::Rect2D& viewport) const;
/**
Returns the pixel area covered by a shape of the given
world space area at the given z value (z must be negative).
*/
float worldToScreenSpaceArea(float area, float z, const class G3D::Rect2D& viewport) const;
/**
Returns the world space 3D viewport corners. These
are at the near clipping plane. The corners are constructed
from the nearPlaneZ, getViewportWidth, and getViewportHeight.
"left" and "right" are from the RbxCamera's perspective.
*/
void get3DViewportCorners(const class G3D::Rect2D& viewport, Vector3& outUR, Vector3& outUL, Vector3& outLL, Vector3& outLR) const;
/**
Returns the image plane depth, <I>s'</I>, given the current field
of view for film of dimensions width x height. See
setImagePlaneDepth for a discussion of worldspace values width and height.
*/
float getImagePlaneDepth(const class G3D::Rect2D& viewport) const;
/**
Returns the world space ray passing through the center of pixel
(x, y) on the image plane. The pixel x and y axes are opposite
the 3D object space axes: (0,0) is the upper left corner of the screen.
They are in viewport coordinates, not screen coordinates.
Integer (x, y) values correspond to
the upper left corners of pixels. If you want to cast rays
through pixel centers, add 0.5 to x and y.
*/
Aya::RbxRay worldRay(float x, float y, const class G3D::Rect2D& viewport) const;
/**
Returns a negative z-value.
*/
inline float nearPlaneZ() const
{
return -nearPlane;
}
/**
Returns a negative z-value.
*/
inline float farPlaneZ() const
{
return -farPlane;
}
inline void setFarPlaneZ(float z)
{
debugAssert(z < 0);
farPlane = -z;
}
inline void setNearPlaneZ(float z)
{
debugAssert(z < 0);
nearPlane = -z;
}
/**
Returns the RbxCamera space width of the viewport.
*/
float getViewportWidth(const class G3D::Rect2D& viewport) const;
/**
Returns the RbxCamera space height of the viewport.
*/
float getViewportHeight(const class G3D::Rect2D& viewport) const;
/**
Read back a RbxCamera space z-value at pixel (x, y) from the depth buffer.
double getZValue(
double x,
double y,
const class G3D::Rect2D& viewport,
double polygonOffset = 0) const;
*/
void setPosition(const Vector3& t);
void lookAt(const Vector3& position, const Vector3& up = Vector3::unitY());
/**
Returns the clipping planes of the frustum, in world space.
The planes have normals facing <B>into</B> the view frustum.
The plane order is guaranteed to be:
Near, Right, Left, Top, Bottom, [Far]
If the far plane is at infinity, the resulting array will have
5 planes, otherwise there will be 6.
The viewport is used only to determine the aspect ratio of the screen; the
absolute dimensions and xy values don't matter.
*/
void getClipPlanes(const G3D::Rect2D& viewport, Array<Plane>& outClip) const;
/**
Returns the world space view frustum, which is a truncated pyramid describing
the volume of space seen by this camera.
*/
void frustum(const G3D::Rect2D& viewport, RbxCamera::Frustum& f) const;
RbxCamera::Frustum frustum(const G3D::Rect2D& viewport) const;
};
} // namespace Aya
#endif

122
engine/3d/src/RbxRay.cpp Normal file
View File

@@ -0,0 +1,122 @@
/**
@file RbxRay.cpp
@maintainer Morgan McGuire, matrix@graphics3d.com
@created 2002-07-12
@edited 2004-03-19
// Aya: from the old g3d (previous to upgrade to g3d 8.0). This version make our code happier.
// It doesn't requires to have unit() vectors.
// We should fix this and remove de need of thise file in the future and use the G3D ones.
*/
// #include "platform.hpp"
#include "RbxRay.hpp"
#include "CollisionDetection.hpp"
namespace Aya
{
RbxRay RbxRay::refract(const Vector3& newOrigin, const Vector3& normal, float iInside, float iOutside) const
{
Vector3 D = m_direction.refractionDirection(normal, iInside, iOutside);
return RbxRay::fromOriginAndDirection(newOrigin + (m_direction + normal * (float)sign(m_direction.dot(normal))) * 0.001f, D);
}
RbxRay RbxRay::reflect(const Vector3& newOrigin, const Vector3& normal) const
{
Vector3 D = m_direction.reflectionDirection(normal);
return RbxRay::fromOriginAndDirection(newOrigin + (D + normal) * 0.001f, D);
}
Vector3 RbxRay::intersectionPlane(const Plane& plane) const
{
/*
float d;
Vector3 normal = plane.normal();
plane.getEquation(normal, d);
float rate = m_direction.dot(normal);
if (rate >= 0.0f) {
return Vector3::inf();
} else {
float t = -(d + m_origin.dot(normal)) / rate;
return m_origin + m_direction * t;
}
*/
Vector3 planeNormal = plane.normal();
float rate = m_direction.dot(planeNormal);
if (fabs(rate) < 1e-6f)
{ // parallel
return Vector3::inf();
}
else
{
float distanceOnRay = (plane.distance() - m_origin.dot(planeNormal)) / rate;
if (distanceOnRay >= 0.0f)
{
return m_origin + m_direction * distanceOnRay;
}
else
{
return Vector3::inf(); // other side of ray
}
}
}
float RbxRay::intersectionTime(const class Sphere& sphere) const
{
Vector3 dummy;
return G3D::CollisionDetection::collisionTimeForMovingPointFixedSphere(m_origin, m_direction, sphere, dummy);
}
float RbxRay::intersectionTime(const class Plane& plane) const
{
Vector3 dummy;
return G3D::CollisionDetection::collisionTimeForMovingPointFixedPlane(m_origin, m_direction, plane, dummy);
}
float RbxRay::intersectionTime(const class Box& box) const
{
Vector3 dummy;
float time = G3D::CollisionDetection::collisionTimeForMovingPointFixedBox(m_origin, m_direction, box, dummy);
if ((time == inf()) && (box.contains(m_origin)))
{
return 0.0f;
}
else
{
return time;
}
}
float RbxRay::intersectionTime(const class AABox& box) const
{
Vector3 dummy;
bool inside;
float time = G3D::CollisionDetection::collisionTimeForMovingPointFixedAABox(m_origin, m_direction, box, dummy, inside);
if ((time == inf()) && inside)
{
return 0.0f;
}
else
{
return time;
}
}
} // namespace Aya

368
engine/3d/src/RbxRay.hpp Normal file
View File

@@ -0,0 +1,368 @@
/**
@file RbxRay.h
RbxRay class
@maintainer Morgan McGuire, matrix@graphics3d.com
@created 2002-07-12
@edited 2006-02-21
// Aya: from the old g3d (previous to upgrade to g3d 8.0). This version make our code happier.
// It doesn't requires to have unit() vectors.
// We should fix this and remove de need of thise file in the future and use the G3D ones.
*/
#ifndef G3D_RbxRay_H
#define G3D_RbxRay_H
#include "platform.hpp"
#include "Vector3.hpp"
#include "Triangle.hpp"
#include "Sphere.hpp"
#include "Box.hpp"
#include "AABox.hpp"
#include "Plane.hpp"
namespace Aya
{
using G3D::AABox;
using G3D::Box;
using G3D::inf;
using G3D::Plane;
using G3D::sign;
using G3D::Sphere;
using G3D::Triangle;
using G3D::Vector3;
/**
A 3D RbxRay.
*/
class RbxRay
{
private:
Vector3 m_origin;
/**
Not unit length
*/
Vector3 m_direction;
public:
RbxRay(const Vector3& origin, const Vector3& direction)
{
this->m_origin = origin;
this->m_direction = direction;
}
RbxRay()
: m_origin(Vector3::zero())
, m_direction(Vector3::zero())
{
}
virtual ~RbxRay() {}
bool operator==(const RbxRay& rhs) const
{
return m_origin == rhs.origin() && m_direction == rhs.direction();
}
bool operator!=(const RbxRay& other) const
{
return m_origin != other.origin() || m_direction != other.direction();
}
inline const Vector3& origin() const
{
return m_origin;
}
/** Not-Unit direction vector. */
inline const Vector3& direction() const
{
return m_direction;
}
inline float length() const
{
return m_direction.length();
}
/// Non Cost versions of above, in lieu of exposing private data.
inline Vector3& origin()
{
return m_origin;
}
/** Not-Unit direction vector. */
inline Vector3& direction()
{
return m_direction;
}
/**
Creates a Ray from a origin and a (nonzero) direction.
*/
static RbxRay fromOriginAndDirection(const Vector3& point, const Vector3& direction)
{
return RbxRay(point, direction);
}
RbxRay unit() const
{
return RbxRay(m_origin, m_direction.unit());
}
/**
Returns the closest point on the Ray to point.
*/
Vector3 closestPoint(const Vector3& point) const
{
float t = m_direction.dot(point - this->m_origin);
if (t < 0)
{
return this->m_origin;
}
else
{
return this->m_origin + m_direction * t;
}
}
/**
Returns the closest distance between point and the Ray
*/
float distance(const Vector3& point) const
{
return (closestPoint(point) - point).magnitude();
}
/**
// modified from G3D - returns intersection of Ray/plane regardless of which side ray is on
*/
Vector3 intersectionPlane(const class Plane& plane) const;
/**
Returns the distance until intersection with the (solid) sphere.
Will be 0 if inside the sphere, inf if there is no intersection.
The ray direction is <B>not</B> normalized. If the ray direction
has unit length, the distance from the origin to intersection
is equal to the time. If the direction does not have unit length,
the distance = time * direction.length().
See also the G3D::CollisionDetection "movingPoint" methods,
which give more information about the intersection.
*/
float intersectionTime(const class Sphere& sphere) const;
float intersectionTime(const class Plane& plane) const;
float intersectionTime(const class Box& box) const;
float intersectionTime(const class AABox& box) const;
/**
The three extra arguments are the weights of vertices 0, 1, and 2
at the intersection point; they are useful for texture mapping
and interpolated normals.
*/
float intersectionTime(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Vector3& edge01, const Vector3& edge02, double& w0,
double& w1, double& w2) const;
/**
Ray-triangle intersection for a 1-sided triangle. Fastest version.
@cite http://www.acm.org/jgt/papers/MollerTrumbore97/
http://www.graphics.cornell.edu/pubs/1997/MT97.html
*/
inline float intersectionTime(
const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, const Vector3& edge01, const Vector3& edge02) const;
inline float intersectionTime(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2) const
{
return intersectionTime(vert0, vert1, vert2, vert1 - vert0, vert2 - vert0);
}
inline float intersectionTime(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, double& w0, double& w1, double& w2) const
{
return intersectionTime(vert0, vert1, vert2, vert1 - vert0, vert2 - vert0, w0, w1, w2);
}
/* One-sided triangle
*/
inline float intersectionTime(const Triangle& triangle) const
{
return intersectionTime(triangle.vertex(0), triangle.vertex(1), triangle.vertex(2), triangle.edge01(), triangle.edge02());
}
inline float intersectionTime(const Triangle& triangle, double& w0, double& w1, double& w2) const
{
return intersectionTime(triangle.vertex(0), triangle.vertex(1), triangle.vertex(2), triangle.edge01(), triangle.edge02(), w0, w1, w2);
}
/** Refracts about the normal
using G3D::Vector3::refractionDirection
and bumps the ray slightly from the newOrigin. */
RbxRay refract(const Vector3& newOrigin, const Vector3& normal, float iInside, float iOutside) const;
/** Reflects about the normal
using G3D::Vector3::reflectionDirection
and bumps the ray slightly from
the newOrigin. */
RbxRay reflect(const Vector3& newOrigin, const Vector3& normal) const;
};
#define EPSILON 0.000001
#define CROSS(dest, v1, v2) \
dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; \
dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; \
dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
#define DOT(v1, v2) (v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2])
#define SUB(dest, v1, v2) \
dest[0] = v1[0] - v2[0]; \
dest[1] = v1[1] - v2[1]; \
dest[2] = v1[2] - v2[2];
inline float RbxRay::intersectionTime(
const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, const Vector3& edge1, const Vector3& edge2) const
{
(void)vert1;
(void)vert2;
// Barycenteric coords
float u, v;
float tvec[3], pvec[3], qvec[3];
// begin calculating determinant - also used to calculate U parameter
CROSS(pvec, m_direction, edge2);
// if determinant is near zero, ray lies in plane of triangle
const float det = DOT(edge1, pvec);
if (det < EPSILON)
{
return (float)inf();
}
// calculate distance from vert0 to ray origin
SUB(tvec, m_origin, vert0);
// calculate U parameter and test bounds
u = DOT(tvec, pvec);
if ((u < 0.0f) || (u > det))
{
// Hit the plane outside the triangle
return (float)inf();
}
// prepare to test V parameter
CROSS(qvec, tvec, edge1);
// calculate V parameter and test bounds
v = DOT(m_direction, qvec);
if ((v < 0.0f) || (u + v > det))
{
// Hit the plane outside the triangle
return (float)inf();
}
// Case where we don't need correct (u, v):
const float t = DOT(edge2, qvec);
if (t >= 0.0f)
{
// Note that det must be positive
return t / det;
}
else
{
// We had to travel backwards in time to intersect
return (float)inf();
}
}
inline float RbxRay::intersectionTime(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, const Vector3& edge1, const Vector3& edge2,
double& w0, double& w1, double& w2) const
{
(void)vert1;
(void)vert2;
// Barycenteric coords
float u, v;
float tvec[3], pvec[3], qvec[3];
// begin calculating determinant - also used to calculate U parameter
CROSS(pvec, m_direction, edge2);
// if determinant is near zero, ray lies in plane of triangle
const float det = DOT(edge1, pvec);
if (det < EPSILON)
{
return (float)inf();
}
// calculate distance from vert0 to ray origin
SUB(tvec, m_origin, vert0);
// calculate U parameter and test bounds
u = DOT(tvec, pvec);
if ((u < 0.0f) || (u > det))
{
// Hit the plane outside the triangle
return (float)inf();
}
// prepare to test V parameter
CROSS(qvec, tvec, edge1);
// calculate V parameter and test bounds
v = DOT(m_direction, qvec);
if ((v < 0.0f) || (u + v > det))
{
// Hit the plane outside the triangle
return (float)inf();
}
float t = DOT(edge2, qvec);
if (t >= 0)
{
const float inv_det = 1.0f / det;
t *= inv_det;
u *= inv_det;
v *= inv_det;
w0 = (1.0f - u - v);
w1 = u;
w2 = v;
return t;
}
else
{
// We had to travel backwards in time to intersect
return (float)inf();
}
}
#undef EPSILON
#undef CROSS
#undef DOT
#undef SUB
} // namespace Aya
#endif

17
engine/3d/src/RbxTime.hpp Normal file
View File

@@ -0,0 +1,17 @@
#ifndef AYA_TIME_H
#define AYA_TIME_H
#include "G3DGameUnits.hpp"
namespace Aya
{
class RbxTime
{
public:
static G3D::RealTime getTick();
private:
static G3D::RealTime m_startTime;
};
} // namespace Aya
#endif

472
engine/3d/src/Rect2D.hpp Normal file
View File

@@ -0,0 +1,472 @@
/**
@file Rect2D.h
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-11-13
@created 2009-11-16
Copyright 2000-2009, Morgan McGuire.
All rights reserved.
*/
#ifndef G3D_Rect2D_h
#define G3D_Rect2D_h
// Linux defines this as a macro
#ifdef border
#undef border
#endif
#include "platform.hpp"
#include "Array.hpp"
#include "Vector2.hpp"
#ifdef _MSC_VER
// Turn off "conditional expression is constant" warning; MSVC generates this
// for debug assertions in inlined methods.
#pragma warning(disable : 4127)
#endif
namespace G3D
{
/**
If you are using this class for pixel rectangles, keep in mind that the last
pixel you can draw to is at x0() + width() - 1.
*/
class Rect2D
{
private:
Vector2 min, max;
/**
Returns true if the whole polygon is clipped.
@param p Value of the point
@param axis Index [0 or 1] of the axis to clip along?
@param clipGreater Are we clipping greater than or less than the line?
@param inPoly Polygon being clipped
@param outPoly The clipped polygon
*/
template<class T>
static bool clipSide2D(const float p, bool clipGreater, int axis, const Array<T>& inPoly, Array<T>& outPoly)
{
outPoly.clear();
int i0 = -1;
Vector2 pt1;
bool c1 = true;
float negate = clipGreater ? -1 : 1;
// Find a point that is not clipped
for (i0 = 0; (i0 < inPoly.length()) && c1; ++i0)
{
pt1 = inPoly[i0];
c1 = (negate * pt1[axis]) < (negate * p);
}
// We incremented i0 one time to many
--i0;
if (c1)
{
// We could not find an unclipped point
return true;
}
outPoly.append(pt1);
// for each point in inPoly,
// if the point is outside the side and the previous one was also outside, continue
// if the point is outside the side and the previous one was inside, cut the line
// if the point is inside the side and the previous one was also inside, append the points
// if the point is inside the side and the previous one was outside, cut the line
for (int i = 1; i <= inPoly.length(); ++i)
{
T pt2 = inPoly[(i + i0) % inPoly.length()];
bool c2 = (negate * pt2[axis]) < (negate * p);
if (c1 ^ c2)
{
if (!c1 && c2 && (i > 1))
{
// Unclipped to clipped trasition and not the first iteration
outPoly.append(pt1);
}
// only one point is clipped, find where the line crosses the clipping plane
float alpha;
if (pt2[axis] == pt1[axis])
{
alpha = 0;
}
else
{
alpha = (p - pt1[axis]) / (pt2[axis] - pt1[axis]);
}
outPoly.append(pt1.lerp(pt2, alpha));
}
else if (!(c1 || c2) && (i != 1))
{
// neither point is clipped (don't do this the first time
// because we appended the first pt before the loop)
outPoly.append(pt1);
}
pt1 = pt2;
c1 = c2;
}
return false;
}
public:
Rect2D()
: min(0, 0)
, max(0, 0)
{
}
/** Creates a rectangle at 0,0 with the given width and height*/
Rect2D(const Vector2& wh)
: min(0, 0)
, max(wh.x, wh.y)
{
}
Rect2D(const Vector2int16& wh)
: min(0, 0)
, max(wh.x, wh.y)
{
}
/** Computes a rectangle that contains both @a a and @a b.
Note that even if @a or @b has zero area, its origin will be included.*/
Rect2D(const Rect2D& a, const Rect2D& b)
{
min = a.min.min(b.min);
max = a.max.max(b.max);
}
/** @brief Uniformly random point on the interior */
Vector2 randomPoint() const
{
return Vector2(uniformRandom(0, max.x - min.x) + min.x, uniformRandom(0, max.y - min.y) + min.y);
}
float width() const
{
return max.x - min.x;
}
float height() const
{
return max.y - min.y;
}
float x0() const
{
return min.x;
}
float x1() const
{
return max.x;
}
float y0() const
{
return min.y;
}
float y1() const
{
return max.y;
}
/** Min, min corner */
Vector2 x0y0() const
{
return min;
}
Vector2 x1y0() const
{
return Vector2(max.x, min.y);
}
Vector2 x0y1() const
{
return Vector2(min.x, max.y);
}
/** Max,max corner */
Vector2 x1y1() const
{
return max;
}
/** Width and height */
Vector2 wh() const
{
return max - min;
}
Vector2 center() const
{
return (max + min) * 0.5;
}
float area() const
{
return width() * height();
}
bool isFinite() const
{
return (min.isFinite() && max.isFinite());
}
Rect2D lerp(const Rect2D& other, float alpha) const
{
Rect2D out;
out.min = min.lerp(other.min, alpha);
out.max = max.lerp(other.max, alpha);
return out;
}
static Rect2D xyxy(float x0, float y0, float x1, float y1)
{
Rect2D r;
r.min.x = G3D::min(x0, x1);
r.min.y = G3D::min(y0, y1);
r.max.x = G3D::max(x0, x1);
r.max.y = G3D::max(y0, y1);
return r;
}
static Rect2D xyxy(const Vector2& v0, const Vector2& v1)
{
Rect2D r;
r.min = v0.min(v1);
r.max = v0.max(v1);
return r;
}
static Rect2D xywh(float x, float y, float w, float h)
{
return xyxy(x, y, x + w, y + h);
}
static Rect2D xywh(const Vector2& v, const Vector2& w)
{
return xyxy(v.x, v.y, v.x + w.x, v.y + w.y);
}
/** Constructs a Rect2D with infinite boundaries.
Use isFinite() to test either min or max.
*/
static Rect2D inf()
{
return xyxy(Vector2::inf(), Vector2::inf());
}
bool contains(const Vector2& v) const
{
return (v.x >= min.x) && (v.y >= min.y) && (v.x <= max.x) && (v.y <= max.y);
}
bool contains(const Rect2D& r) const
{
return (min.x <= r.min.x) && (min.y <= r.min.y) && (max.x >= r.max.x) && (max.y >= r.max.y);
}
/** True if there is non-zero area to the intersection between @a this and @a r.
Note that two rectangles that are adjacent do not intersect because there is
zero area to the overlap, even though one of them "contains" the corners of the other.*/
bool intersects(const Rect2D& r) const
{
return (min.x < r.max.x) && (min.y < r.max.y) && (max.x > r.min.x) && (max.y > r.min.y);
}
/** Like intersection, but counts the adjacent case as touching. */
bool intersectsOrTouches(const Rect2D& r) const
{
return (min.x <= r.max.x) && (min.y <= r.max.y) && (max.x >= r.min.x) && (max.y >= r.min.y);
}
Rect2D operator*(float s) const
{
return xyxy(min.x * s, min.y * s, max.x * s, max.y * s);
}
Rect2D operator/(float s) const
{
return xyxy(min / s, max / s);
}
Rect2D operator/(const Vector2& s) const
{
return xyxy(min / s, max / s);
}
Rect2D operator+(const Vector2& v) const
{
return xyxy(min + v, max + v);
}
Rect2D operator-(const Vector2& v) const
{
return xyxy(min - v, max - v);
}
bool operator==(const Rect2D& other) const
{
return (min == other.min) && (max == other.max);
}
bool operator!=(const Rect2D& other) const
{
return (min != other.min) || (max != other.max);
}
/** Returns the corners in the order: (min,min), (max,min), (max,max), (min,max). */
Vector2 corner(int i) const
{
debugAssert(i >= 0 && i < 4);
switch (i & 3)
{
case 0:
return Vector2(min.x, min.y);
case 1:
return Vector2(max.x, min.y);
case 2:
return Vector2(max.x, max.y);
case 3:
return Vector2(min.x, max.y);
default:
// Should never get here
return Vector2(0, 0);
}
}
/** @deprecated
@sa expand() */
Rect2D border(float delta) const
{
return Rect2D::xywh(x0() + delta, y0() + delta, width() - 2.0f * delta, height() - 2.0f * delta);
}
/** Returns a new Rect2D that is bigger/smaller by the specified amount
(negative is shrink.) */
Rect2D expand(float delta) const
{
float newX = x0() - delta;
float newY = y0() - delta;
float newW = width() + 2.0f * delta;
float newH = height() + 2.0f * delta;
if (newW < 0.0f)
{
newX = (x0() + width()) / 2.0f;
newW = 0.0f;
}
if (newH < 0.0f)
{
newY = (y0() + height()) / 2.0f;
newH = 0.0f;
}
return Rect2D::xywh(newX, newY, newW, newH);
}
/**
Clips so that the rightmost point of the outPoly is at rect.x1 (e.g. a 800x600 window produces
rightmost point 799, not 800). The results are suitable for pixel rendering if iRounded.
Templated so that it will work for Vector2,3,4 (the z and w components are interpolated linearly).
The template parameter must define T.lerp and contain x and y components.
If the entire polygon is clipped by a single side, the result will be empty.
The result might also have zero area but not be empty.
*/
template<class T>
void clip(const Array<T>& inPoly, Array<T>& outPoly) const
{
const bool greaterThan = true;
const bool lessThan = false;
const int X = 0;
const int Y = 1;
Array<T> temp;
bool entirelyClipped = clipSide2D(x0(), lessThan, X, inPoly, temp) || clipSide2D(x1(), greaterThan, X, temp, outPoly) ||
clipSide2D(y0(), lessThan, Y, outPoly, temp) || clipSide2D(y1(), greaterThan, Y, temp, outPoly);
if (entirelyClipped)
{
outPoly.clear();
}
}
/** Returns the largest, centered Rect2D that can fit inside this
while maintaining the aspect ratio of x:y. Convenient for
displaying images in odd-shaped windows.
*/
Rect2D largestCenteredSubRect(float ww, float hh) const
{
float textureAspect = hh / ww;
float viewAspect = height() / width();
if (viewAspect > textureAspect)
{
// The view is too tall
float h = width() * textureAspect;
float y = (height() - h) / 2;
return Rect2D::xywh(0, y, width(), h) + corner(0);
}
else
{
// The view is too wide
float w = height() / textureAspect;
float x = (width() - w) / 2;
return Rect2D::xywh(x, 0, w, height()) + corner(0);
}
}
/**
Returns the overlap region between the two rectangles. This may have zero area
if they do not intersect. See the two-Rect2D constructor for a way to compute
a union-like rectangle.
*/
Rect2D intersect(const Rect2D& other) const
{
if (intersects(other))
{
return Rect2D::xyxy(min.max(other.min), max.min(other.max));
}
else
{
return Rect2D::xywh(0, 0, 0, 0);
}
}
};
typedef Rect2D AABox2D;
} // namespace G3D
#endif

214
engine/3d/src/Set.hpp Normal file
View File

@@ -0,0 +1,214 @@
/**
@file Set.h
Hash set
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2001-12-09
@edited 2009-06-10
*/
#ifndef G3D_Set_h
#define G3D_Set_h
#include "platform.hpp"
#include "Table.hpp"
#include "MemoryManager.hpp"
#include <assert.h>
#include <string>
namespace G3D
{
/**
An unordered data structure that has at most one of each element.
Provides O(1) time insert, remove, and member test (contains).
Set uses G3D::Table internally, which means that the template type T
must define a hashCode and operator== function. See G3D::Table for
a discussion of these functions.
*/
// There is not copy constructor or assignment operator defined because
// the default ones are correct for Set.
template<class T, class HashFunc = HashTrait<T>, class EqualsFunc = EqualsTrait<T>>
class Set
{
/**
If an object is a member, it is contained in
this table.
*/
Table<T, bool, HashFunc, EqualsFunc> memberTable;
public:
void clearAndSetMemoryManager(const MemoryManager::Ref& m)
{
memberTable.clearAndSetMemoryManager(m);
}
virtual ~Set() {}
int size() const
{
return (int)memberTable.size();
}
bool contains(const T& member) const
{
return memberTable.containsKey(member);
}
/**
Inserts into the table if not already present.
Returns true if this is the first time the element was added.
*/
bool insert(const T& member)
{
bool isNew = false;
memberTable.getCreate(member, isNew) = true;
return isNew;
}
/**
Returns true if the element was present and removed. Returns false
if the element was not present.
*/
bool remove(const T& member)
{
return memberTable.remove(member);
}
/** If @a member is present, sets @a removed to the element
being removed and returns true. Otherwise returns false
and does not write to @a removed. This is useful when building
efficient hashed data structures that wrap Set.
*/
bool getRemove(const T& member, T& removed)
{
bool ignore;
return memberTable.getRemove(member, removed, ignore);
}
/** If a value that is EqualsFunc to @a member is present, returns a pointer to the
version stored in the data structure, otherwise returns NULL.
*/
const T* getPointer(const T& member) const
{
return memberTable.getKeyPointer(member);
}
Array<T> getMembers() const
{
return memberTable.getKeys();
}
void getMembers(Array<T>& keyArray) const
{
memberTable.getKeys(keyArray);
}
void clear()
{
memberTable.clear();
}
void deleteAll()
{
getMembers().deleteAll();
clear();
}
/**
C++ STL style iterator variable. See begin().
*/
class Iterator
{
private:
friend class Set<T>;
// Note: this is a Table iterator, we are currently defining
// Set iterator
typename Table<T, bool>::Iterator it;
Iterator(const typename Table<T, bool>::Iterator& it)
: it(it)
{
}
public:
inline bool operator!=(const Iterator& other) const
{
return !(*this == other);
}
bool hasMore() const
{
return it.hasMore();
}
bool operator==(const Iterator& other) const
{
return it == other.it;
}
/**
Pre increment.
*/
Iterator& operator++()
{
++it;
return *this;
}
/**
Post increment (slower than preincrement).
*/
Iterator operator++(int)
{
Iterator old = *this;
++(*this);
return old;
}
const T& operator*() const
{
return it->key;
}
T* operator->() const
{
return &(it->key);
}
operator T*() const
{
return &(it->key);
}
};
/**
C++ STL style iterator method. Returns the first member.
Use preincrement (++entry) to get to the next element.
Do not modify the set while iterating.
*/
Iterator begin() const
{
return Iterator(memberTable.begin());
}
/**
C++ STL style iterator method. Returns one after the last iterator
element.
*/
const Iterator end() const
{
return Iterator(memberTable.end());
}
};
} // namespace G3D
#endif

View File

@@ -0,0 +1,206 @@
/**
@file SmallArray.h
@created 2009-04-26
@edited 2010-02-26
Copyright 2000-2010, Morgan McGuire, http://graphics.cs.williams.edu
All rights reserved.
*/
#ifndef G3D_SmallArray_h
#define G3D_SmallArray_h
#include "platform.hpp"
#include "Array.hpp"
#include "MemoryManager.hpp"
namespace G3D
{
/** Embeds \a N elements to reduce allocation time and increase
memory coherence when working with arrays of arrays.
Offers a limited subset of the functionality of G3D::Array.*/
template<class T, int N>
class SmallArray
{
private:
int m_size;
/** First N elements */
T m_embedded[N];
/** Remaining elements */
Array<T> m_rest;
public:
SmallArray()
: m_size(0)
{
}
inline int size() const
{
return m_size;
}
void resize(int n, bool shrinkIfNecessary = true)
{
m_rest.resize(std::max(0, n - N), shrinkIfNecessary);
m_size = n;
}
void clear(bool shrinkIfNecessary = true)
{
resize(0, shrinkIfNecessary);
}
void clearAndSetMemoryManager(MemoryManager::Ref& m)
{
clear();
m_rest.clearAndSetMemoryManager(m);
}
inline T& operator[](int i)
{
debugAssert(i < m_size && i >= 0);
if (i < N)
{
return m_embedded[i];
}
else
{
return m_rest[i - N];
}
}
inline const T& operator[](int i) const
{
debugAssert(i < m_size && i >= 0);
if (i < N)
{
return m_embedded[i];
}
else
{
return m_rest[i - N];
}
}
inline void push(const T& v)
{
++m_size;
if (m_size <= N)
{
m_embedded[m_size - 1] = v;
}
else
{
m_rest.append(v);
}
}
inline void append(const T& v)
{
push(v);
}
void fastRemove(int i, bool shrinkIfNecessary = false)
{
debugAssert(i < m_size && i >= 0);
if (i < N)
{
if (m_size <= N)
{
// Exclusively embedded
m_embedded[i] = m_embedded[m_size - 1];
}
else
{
// Move one down from the rest array
m_embedded[i] = m_rest.pop();
}
}
else
{
// Removing from the rest array
m_rest.fastRemove(i - N, shrinkIfNecessary);
}
--m_size;
}
T pop()
{
debugAssert(m_size > 0);
if (m_size <= N)
{
// Popping from embedded, don't need a temporary
--m_size;
return m_embedded[m_size];
}
else
{
// Popping from rest
--m_size;
return m_rest.pop();
}
}
inline void popDiscard()
{
debugAssert(m_size > 0);
if (m_size > N)
{
m_rest.popDiscard();
}
--m_size;
}
inline T& next()
{
++m_size;
if (m_size <= N)
{
return m_embedded[m_size - 1];
}
else
{
return m_rest.next();
}
}
bool contains(const T& value) const
{
for (int i = std::min(m_size, N) - 1; i >= 0; --i)
{
if (m_embedded[i] == value)
{
return true;
}
}
return m_rest.contains(value);
}
template<int MIN_ELEMENTS, int MIN_BYTES>
SmallArray<T, N>& operator=(const Array<T, MIN_ELEMENTS, MIN_BYTES>& src)
{
resize(src.size());
for (int i = 0; i < src.size(); ++i)
{
(*this)[i] = src[i];
}
return *this;
}
inline const T& last() const
{
return (*this)[size() - 1];
}
inline T& last()
{
return (*this)[size() - 1];
}
};
} // namespace G3D
#endif

Some files were not shown because too many files have changed in this diff Show More