1/*
2 * Copyright 2006-2007, 2023, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan A��mus <superstippi@gmx.de>
7 *		Zardshard
8 */
9
10
11#include "PerspectiveTransformer.h"
12
13#include <new>
14#include <stdio.h>
15
16#include <agg_basics.h>
17#include <agg_bounding_rect.h>
18#include <Message.h>
19
20#include "Shape.h"
21
22
23_USING_ICON_NAMESPACE
24using std::nothrow;
25
26
27PerspectiveTransformer::PerspectiveTransformer(VertexSource& source, Shape* shape)
28	: Transformer("Perspective"),
29	  PathTransformer(source),
30	  Perspective(source, *this),
31	  fShape(shape)
32#ifdef ICON_O_MATIC
33	, fInverted(false)
34#endif
35{
36#ifdef ICON_O_MATIC
37	if (fShape != NULL) {
38		fShape->AcquireReference();
39		fShape->AddObserver(this);
40		ObjectChanged(fShape); // finish initialization
41	}
42#endif
43}
44
45
46PerspectiveTransformer::PerspectiveTransformer(
47		VertexSource& source, Shape* shape, BMessage* archive)
48	: Transformer(archive),
49	  PathTransformer(source),
50	  Perspective(source, *this),
51	  fShape(shape)
52#ifdef ICON_O_MATIC
53	, fInverted(false)
54#endif
55{
56	double matrix[9];
57	for (int i = 0; i < 9; i++) {
58		if (archive->FindDouble("matrix", i, &matrix[i]) != B_OK)
59			matrix[i] = 0;
60	}
61	load_from(matrix);
62
63#ifdef ICON_O_MATIC
64	if (fShape != NULL) {
65		fShape->AcquireReference();
66		fShape->AddObserver(this);
67		ObjectChanged(fShape); // finish initialization
68	}
69#endif
70}
71
72
73PerspectiveTransformer::PerspectiveTransformer(const PerspectiveTransformer& other)
74#ifdef ICON_O_MATIC
75	: Transformer(other.Name()),
76#else
77	: Transformer(""),
78#endif
79	  PathTransformer(*other.fSource),
80	  Perspective(*fSource, *this),
81	  fShape(other.fShape)
82#ifdef ICON_O_MATIC
83	, fInverted(other.fInverted),
84	  fFromBox(other.fFromBox),
85	  fToLeftTop(other.fToLeftTop),
86	  fToRightTop(other.fToRightTop),
87	  fToLeftBottom(other.fToLeftBottom),
88	  fToRightBottom(other.fToRightBottom),
89	  fValid(other.fValid)
90#endif
91{
92	double matrix[9];
93	other.store_to(matrix);
94	load_from(matrix);
95
96#ifdef ICON_O_MATIC
97	if (fShape != NULL) {
98		fShape->AcquireReference();
99		fShape->AddObserver(this);
100	}
101#endif
102}
103
104
105PerspectiveTransformer::~PerspectiveTransformer()
106{
107#ifdef ICON_O_MATIC
108	if (fShape != NULL) {
109		fShape->RemoveObserver(this);
110		fShape->ReleaseReference();
111	}
112#endif
113}
114
115
116// #pragma mark -
117
118
119Transformer*
120PerspectiveTransformer::Clone() const
121{
122	return new (nothrow) PerspectiveTransformer(*this);
123}
124
125
126// #pragma mark -
127
128
129void
130PerspectiveTransformer::rewind(unsigned path_id)
131{
132	Perspective::rewind(path_id);
133}
134
135
136unsigned
137PerspectiveTransformer::vertex(double* x, double* y)
138{
139#ifdef ICON_O_MATIC
140	if (fValid)
141		return Perspective::vertex(x, y);
142	else
143		return agg::path_cmd_stop;
144#else
145	return Perspective::vertex(x, y);
146#endif
147}
148
149
150void
151PerspectiveTransformer::SetSource(VertexSource& source)
152{
153	PathTransformer::SetSource(source);
154	Perspective::attach(source);
155
156#ifdef ICON_O_MATIC
157	ObjectChanged(fShape);
158#endif
159}
160
161
162double
163PerspectiveTransformer::ApproximationScale() const
164{
165	return fSource->ApproximationScale() * scale();
166}
167
168
169// #pragma mark -
170
171
172void
173PerspectiveTransformer::Invert()
174{
175#ifdef ICON_O_MATIC
176	fInverted = !fInverted;
177
178	// TODO: degenerate matrices may not be adequately handled
179	bool degenerate = !invert();
180	fValid = fValid && !degenerate;
181#else
182	invert();
183#endif
184}
185
186
187// #pragma mark -
188
189
190#ifdef ICON_O_MATIC
191
192status_t
193PerspectiveTransformer::Archive(BMessage* into, bool deep) const
194{
195	status_t ret = Transformer::Archive(into, deep);
196
197	into->what = archive_code;
198
199	double matrix[9];
200	store_to(matrix);
201
202	for (int i = 0; i < 9; i++) {
203		if (ret == B_OK)
204			ret = into->AddDouble("matrix", matrix[i]);
205	}
206
207	return ret;
208}
209
210
211// #prama mark -
212
213
214void
215PerspectiveTransformer::ObjectChanged(const Observable* object)
216{
217	if (fInverted) {
218		printf("calculating the validity or bounding box of an inverted "
219			"perspective transformer is currently unsupported.");
220		return;
221	}
222
223	uint32 pathID[1];
224	pathID[0] = 0;
225	double left, top, right, bottom;
226	agg::bounding_rect(*fSource, pathID, 0, 1, &left, &top, &right, &bottom);
227	BRect newFromBox = BRect(left, top, right, bottom);
228
229	// Stop if nothing we care about has changed
230	// TODO: Can this be done earlier? It would be nice to avoid having to
231	// recalculate the bounding box before realizing nothing needs to be done.
232    if (fFromBox == newFromBox)
233		return;
234
235	fFromBox = newFromBox;
236
237	_CheckValidity();
238
239	double x = fFromBox.left; double y = fFromBox.top;
240	Transform(&x, &y);
241	fToLeftTop = BPoint(x, y);
242
243	x = fFromBox.right; y = fFromBox.top;
244	Transform(&x, &y);
245	fToRightTop = BPoint(x, y);
246
247	x = fFromBox.left; y = fFromBox.bottom;
248	Transform(&x, &y);
249	fToLeftBottom = BPoint(x, y);
250
251	x = fFromBox.right; y = fFromBox.bottom;
252	Transform(&x, &y);
253	fToRightBottom = BPoint(x, y);
254}
255
256
257// #pragma mark -
258
259
260void
261PerspectiveTransformer::TransformTo(
262	BPoint leftTop, BPoint rightTop, BPoint leftBottom, BPoint rightBottom)
263{
264	fToLeftTop = leftTop;
265	fToRightTop = rightTop;
266	fToLeftBottom = leftBottom;
267	fToRightBottom = rightBottom;
268
269	double quad[8] = {
270		fToLeftTop.x, fToLeftTop.y,
271		fToRightTop.x, fToRightTop.y,
272		fToRightBottom.x, fToRightBottom.y,
273		fToLeftBottom.x, fToLeftBottom.y
274	};
275
276	if (!fInverted) {
277		rect_to_quad(
278			fFromBox.left, fFromBox.top,
279			fFromBox.right, fFromBox.bottom, quad);
280	} else {
281		quad_to_rect(quad,
282			fFromBox.left, fFromBox.top,
283			fFromBox.right, fFromBox.bottom);
284	}
285
286	_CheckValidity();
287	Notify();
288}
289
290
291// #pragma mark -
292
293
294void
295PerspectiveTransformer::_CheckValidity()
296{
297	// Checks that none of the points are too close to the camera. These tend to
298	// lead to very big numbers or a divide by zero error. Also checks that all
299	// points are on the same side of the camera. Transformations with points on
300	// different sides of the camera look weird and tend to cause crashes.
301
302	fValid = true;
303	double w;
304	bool positive;
305
306	if (!fInverted) {
307		w = fFromBox.left * w0 + fFromBox.top * w1 + w2;
308		fValid &= (fabs(w) > 0.00001);
309		positive = w > 0;
310
311		w = fFromBox.right * w0 + fFromBox.top * w1 + w2;
312		fValid &= (fabs(w) > 0.00001);
313		fValid &= (w>0)==positive;
314
315		w = fFromBox.left * w0 + fFromBox.bottom * w1 + w2;
316		fValid &= (fabs(w) > 0.00001);
317		fValid &= (w>0)==positive;
318
319		w = fFromBox.right * w0 + fFromBox.bottom * w1 + w2;
320		fValid &= (fabs(w) > 0.00001);
321		fValid &= (w>0)==positive;
322	} else {
323		w = fToLeftTop.x * w0 + fToLeftTop.y * w1 + w2;
324		fValid &= (fabs(w) > 0.00001);
325		positive = w > 0;
326
327		w = fToRightTop.x * w0 + fToRightTop.y * w1 + w2;
328		fValid &= (fabs(w) > 0.00001);
329		fValid &= (w>0)==positive;
330
331		w = fToLeftBottom.x * w0 + fToLeftBottom.y * w1 + w2;
332		fValid &= (fabs(w) > 0.00001);
333		fValid &= (w>0)==positive;
334
335		w = fToRightBottom.x * w0 + fToRightBottom.y * w1 + w2;
336		fValid &= (fabs(w) > 0.00001);
337		fValid &= (w>0)==positive;
338	}
339}
340
341#endif // ICON_O_MATIC
342