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 24 using std::nothrow; 25 26 27 PerspectiveTransformer::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 46 PerspectiveTransformer::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 73 PerspectiveTransformer::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 105 PerspectiveTransformer::~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 119 Transformer* 120 PerspectiveTransformer::Clone() const 121 { 122 return new (nothrow) PerspectiveTransformer(*this); 123 } 124 125 126 // #pragma mark - 127 128 129 void 130 PerspectiveTransformer::rewind(unsigned path_id) 131 { 132 Perspective::rewind(path_id); 133 } 134 135 136 unsigned 137 PerspectiveTransformer::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 150 void 151 PerspectiveTransformer::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 162 double 163 PerspectiveTransformer::ApproximationScale() const 164 { 165 return fSource.ApproximationScale() * scale(); 166 } 167 168 169 // #pragma mark - 170 171 172 void 173 PerspectiveTransformer::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 192 status_t 193 PerspectiveTransformer::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 214 void 215 PerspectiveTransformer::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 260 void 261 PerspectiveTransformer::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 294 void 295 PerspectiveTransformer::_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