/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "common/file.h"
#include "tetraedge/te/te_bezier_curve.h"
#include "tetraedge/te/te_mesh.h"
#include "tetraedge/te/te_renderer.h"
#include "tetraedge/tetraedge.h"
namespace Tetraedge {
TeBezierCurve::TeBezierCurve() : _length(0.0), _rawLength(0.0), _lengthNeedsUpdate(true),
_rawLengthNeedsUpdate(true), _numIterations(1000) {
}
//int TeBezierCurve::bounds(int start);
void TeBezierCurve::clear() {
_lengthNeedsUpdate = true;
_rawLengthNeedsUpdate = true;
_length = 0.0;
_controlPoints.clear();
}
void TeBezierCurve::draw() {
if (!worldVisible() || _controlPoints.empty())
return;
Common::SharedPtr mesh1(TeMesh::makeInstance());
Common::SharedPtr mesh2(TeMesh::makeInstance());
uint npoints = _controlPoints.size();
mesh1->setConf(npoints, npoints, TeMesh::MeshMode_Points, 0, 0);
for (uint i = 0; i < npoints; i++) {
mesh1->setVertex(i, _controlPoints[i]);
mesh1->setIndex(i, i);
}
mesh2->setConf(npoints, npoints, TeMesh::MeshMode_LineStrip, 0, 0);
for (uint i = 0; i < npoints; i++) {
mesh2->setVertex(i, _controlPoints[i]);
mesh2->setNormal(i, TeVector3f32(0.0f, 1.0f, 0.0));
mesh2->setIndex(i, i);
}
TeRenderer *renderer = g_engine->getRenderer();
const TeColor prevColor = renderer->currentColor();
renderer->pushMatrix();
renderer->multiplyMatrix(worldTransformationMatrix());
renderer->setCurrentColor(TeColor(0, 0xff, 0xff, 0xff));
mesh2->draw();
renderer->setCurrentColor(TeColor(0xff, 0, 0xff, 0xff));
mesh1->draw();
renderer->popMatrix();
renderer->setCurrentColor(prevColor);
}
float TeBezierCurve::length() {
if (_lengthNeedsUpdate) {
_length = 0.0;
_lengthNeedsUpdate = false;
_lengths.clear();
TeVector3f32 lastpt = _controlPoints[0];
lastpt.y() = 0;
for (uint i = 0; i < _numIterations; i++) {
float amount = (float)i / _numIterations;
TeVector3f32 pt = retrievePoint(amount);
pt.y() = 0;
float len = (lastpt - pt).length();
_length += len;
_lengths.push_back(_length);
lastpt = pt;
}
}
return _length;
}
void TeBezierCurve::pseudoTangent(float offset, TeVector3f32 &v1, TeVector3f32 &v2) {
const float step = 1.0f / _numIterations;
if (step + offset <= 1.0f) {
v1 = retrievePoint(offset);
v2 = retrievePoint(offset + step);
} else {
v1 = retrievePoint(offset - step);
v2 = retrievePoint(offset);
}
}
float TeBezierCurve::rawLength() {
if (_rawLengthNeedsUpdate) {
_rawLengthNeedsUpdate = false;
_rawLength = 0.0;
_rawLengths.clear();
_rawLengths.push_back(0.0);
for (uint i = 1; i < _controlPoints.size(); i++) {
const TeVector3f32 diff = _controlPoints[i] - _controlPoints[i - 1];
_rawLength += diff.length();
_rawLengths.push_back(_rawLength);
}
}
return _rawLength;
}
TeVector3f32 TeBezierCurve::retrievePoint(float offset) {
const int npoints = _controlPoints.size();
// Simple cases for small numbers of points.
if (npoints == 0)
return TeVector3f32();
else if (npoints == 1)
return _controlPoints[0];
else if (npoints == 2)
return _controlPoints[0] + (_controlPoints[1] - _controlPoints[0]) * offset;
// else, there are at least 3 points so need to actually interpolate.
const float rawlen = rawLength();
float proportion = 0.0f;
int startpt = 0;
while (startpt < npoints) {
proportion = _rawLengths[startpt] / rawlen;
if (proportion >= offset)
break;
startpt++;
}
float t;
if (proportion == offset) {
// Exactly on a point
t = 0.0f;
} else {
// Proportion between two points
float p1 = _rawLengths[startpt - 1];
float p2 = _rawLengths[startpt];
t = (rawlen * offset - p1) / (p2 - p1);
startpt--;
}
// Collect 4 points around the startpt (1 before, 2 after)
TeVector3f32 points[4];
const int maxPt = _controlPoints.size() - 1;
for (int p = 0; p < 4; p++) {
int ptno = CLIP(startpt + p - 1, 0, maxPt);
points[p] = _controlPoints[ptno];
}
// If we hit the end, linearly extend the last gradient.
if (startpt <= 0)
points[0] += (points[1] - points[2]);
if (startpt + 1 >= maxPt)
points[3] += (points[2] - points[1]);
return hermiteInterpolate(t, points, 0.0, 0.0);
}
void TeBezierCurve::setControlPoints(const Common::Array &points) {
_lengthNeedsUpdate = true;
_rawLengthNeedsUpdate = true;
_controlPoints = points;
}
void TeBezierCurve::setNbIterations(uint iterations) {
_lengthNeedsUpdate = true;
_rawLengthNeedsUpdate = true;
_numIterations = iterations;
}
/*static*/
void TeBezierCurve::serialize(Common::WriteStream &stream, const TeBezierCurve &curve) {
error("TODO: Implement TeBezierCurve::serialize");
}
/*static*/
void TeBezierCurve::deserialize(Common::ReadStream &stream, TeBezierCurve &curve) {
Te3DObject2::deserialize(stream, curve);
curve._lengthNeedsUpdate = false;
curve._length = stream.readFloatLE();
uint32 npoints = stream.readUint32LE();
if (npoints > 1000000)
error("TeBezierCurve::deserialize improbable number of control ponts %d", npoints);
for (uint i = 0; i < npoints; i++) {
TeVector3f32 vec;
TeVector3f32::deserialize(stream, vec);
curve._controlPoints.push_back(vec);
}
}
void TeBezierCurve::loadBin(TetraedgeFSNode &node) {
Common::ScopedPtr file(node.createReadStream());
Common::String fname = node.getPath().baseName();
if (fname.size() < 4)
error("TeBezierCurve::loadBin fname %s is too short", fname.c_str());
setName(fname.substr(0, fname.size() - 4));
// Load position / rotation / size
Te3DObject2::deserialize(*file, *this, false);
// Then it resets them?
setPosition(TeVector3f32());
setRotation(TeQuaternion());
setSize(TeVector3f32(1, 1, 1));
_lengthNeedsUpdate = true;
uint32 npoints = file->readUint32LE();
if (npoints > 1000000)
error("TeBezierCurve::loadBin improbable number of control ponts %d", npoints);
for (uint i = 0; i < npoints; i++) {
TeVector3f32 vec;
TeVector3f32::deserialize(*file, vec);
_controlPoints.push_back(vec);
}
}
/*static*/
TeVector3f32 TeBezierCurve::hermiteInterpolate(float t, const TeVector3f32 *points, float param_4, float param_5) {
assert(points);
const TeVector3f32 delta1 = ((points[1] - points[0]) * (param_5 + 1.0) * (1.0 - param_4)) / 2.0;
const TeVector3f32 delta2a = ((points[2] - points[1]) * (1.0 - param_5) * (1.0 - param_4)) / 2.0;
const TeVector3f32 delta2b = ((points[2] - points[1]) * (param_5 + 1.0) * (1.0 - param_4)) / 2.0;
const TeVector3f32 delta3 = ((points[3] - points[2]) * (1.0 - param_5) * (1.0 - param_4)) / 2.0;
const TeVector3f32 x1 = delta1 + delta2a;
const TeVector3f32 x2 = delta2b + delta3;
const float t2 = t * t;
const float t3 = t * t * t;
const TeVector3f32 h1a = points[1] * ((t3 + t3) - t2 * 3.0 + 1.0);
const TeVector3f32 h1b = x1 * ((t3 - (t2 + t2)) + t);
const TeVector3f32 h1 = (h1a + h1b) + (x2 * (t3 - t2));
return h1 + (points[2] * (t3 * -2.0 + t2 * 3.0));
}
} // end namespace Tetraedge