-
Notifications
You must be signed in to change notification settings - Fork 1
/
Drawing.pde
457 lines (398 loc) · 14 KB
/
Drawing.pde
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
/**
* A Drawing is a object that contains 3D stroke data.
* The Drawing consists of a list of Strokes which consists of a series of Points
* @author Kelly Egan
* @version 0.1
*/
import processing.core.PApplet;
class Drawing {
List<Stroke> strokes;
List<Stroke> undos;
Stroke currentStroke;
float minimumDistance;
PVector screenBounds;
PVector up;
PVector realScale;
PApplet app;
/**
* Creates an empty Drawing from the "template.gml" file
* The currentStroke is set to null until drawing begins
* And there is no Stroke or Point data
*/
Drawing(PApplet a) {
this(a, "template.gml");
}
/**
* Creates a Drawing from a GML (Graffiti Markup Language) file.
* @param filepath Path to the GML file.
*/
Drawing(PApplet a, String filepath ) {
app = a;
strokes = new ArrayList<Stroke>();
undos = new ArrayList<Stroke>();
minimumDistance = 10;
load( filepath );
}
/**
* Loads an GML (Graffiti Markup Language) file into the Drawing object
* @param filepath Path to the GML file.
*/
void load(String filepath) {
String filename = new File(filepath).getName();
println("Loading " + filename + "...");
int pointCount = 0;
int strokeCount = 0;
XML gml = loadXML( filepath );
XML drawing = gml.getChild("tag/drawing");
//Set up environmental data (screenBounds, up and realScale)
if( gml.getChild("tag/header/environment/screenBounds") != null ) {
screenBounds = xmlToVector( gml.getChild("tag/header/environment/screenBounds") );
} else {
screenBounds = new PVector(width, height, max(width, height) );
}
if( gml.getChild("tag/header/environment/up") != null ) {
up = xmlToVector( gml.getChild("tag/header/environment/up") );
} else {
up = new PVector(0, -1, 0);
}
if( gml.getChild("tag/header/environment/realScale") != null ) {
realScale = xmlToVector( gml.getChild("tag/header/environment/realScale") );
} else {
realScale = new PVector(200, 200, 200);
}
//Load strokes
for( XML strokeElement : drawing.getChildren("stroke") ) {
Brush brushStyle = new Brush();
if( strokeElement.getChild("brush") != null ) {
//Check if there is a uniqueStyleID value and if so apply it to Brush
try {
brushStyle.setName( strokeElement.getChild("brush/uniqueStyleID").getContent() );
} catch( Exception e ) {
System.err.println("ERROR: uniqueStyleID data not found for Brush.");
}
//Check if there is a width value and if so apply it to Brush strokeWeight
try {
brushStyle.setWeight( strokeElement.getChild("brush/width").getIntContent() );
} catch( Exception e ) {
System.err.println("ERROR: Width data not found for Brush.");
}
//Check if there are r, g, and b color values and if so apply it to Brush color
try {
int r = strokeElement.getChild("brush/color/r").getIntContent();
int g = strokeElement.getChild("brush/color/g").getIntContent();
int b = strokeElement.getChild("brush/color/b").getIntContent();
int a = strokeElement.getChild("brush/color/a").getIntContent();
brushStyle.setColor( color(r,g,b,a) );
} catch( Exception e ) {
System.err.println("ERROR: Color data not found for Brush.");
}
}
startStroke( brushStyle );
//Load points
for( XML ptElement : strokeElement.getChildren("pt") ) {
PVector location = xmlToVector( ptElement );
if( location != null ) {
location = scaleToScreen( location );
float time = 0.0;
if( ptElement.getChild("t") != null ) {
time = ptElement.getChild("t").getFloatContent();
} else if( ptElement.getChild("time") != null ) {
time = ptElement.getChild("time").getFloatContent();
} else {
System.err.println("ERROR: Couldn't find <t> or <time> elements in \"" + filename + "\". Setting time to 0.0.");
}
addPoint( time, location, true ); //Ignore minimum distance and just reads in points as they are stored.
pointCount++;
} else {
System.err.println("ERROR: <pt> element coordinates not valid in \"" + filename + "\". Couldn't create point.");
}
}
endStroke();
strokeCount++;
}
println("Loaded " + pointCount + " points and " + strokeCount + " strokes.");
println("screenBounds: " + screenBounds + " up: " + up + " realScale: " + realScale);
}
/**
* Save Drawing object to GML file
* @param filepath Location to save GML file
*/
void save( String filepath ) {
XML gml = loadXML("template.gml");
XML drawing = gml.getChild("tag/drawing");
XML screenBoundsElement = gml.getChild("tag/header/environment/screenBounds");
screenBoundsElement.getChild("x").setFloatContent( screenBounds.x );
screenBoundsElement.getChild("y").setFloatContent( screenBounds.y );
screenBoundsElement.getChild("z").setFloatContent( screenBounds.z );
//up vector follows processing convention (0, -1, 0)
XML realScaleElement = gml.getChild("tag/header/environment/realScale");
realScaleElement.getChild("x").setFloatContent( realScale.x );
realScaleElement.getChild("y").setFloatContent( realScale.y );
realScaleElement.getChild("z").setFloatContent( realScale.z );
for( Stroke stroke : strokes ) {
if( stroke.points.size() > 0 ) {
XML strokeElement = drawing.addChild("stroke");
//Add Brush data
XML brushElement = strokeElement.addChild("brush");
brushElement.addChild("uniqueStyleID").setContent( stroke.style.getName() );
brushElement.addChild("width").setFloatContent( stroke.style.getWeight() );
XML brushColor = brushElement.addChild("color");
brushColor.addChild("r").setIntContent( (int)red( stroke.style.getColor() ) );
brushColor.addChild("g").setIntContent( (int)green( stroke.style.getColor() ) );
brushColor.addChild("b").setIntContent( (int)blue( stroke.style.getColor() ) );
brushColor.addChild("a").setIntContent( (int)alpha( stroke.style.getColor() ) );
for( Point point : stroke.points ) {
XML ptElement = vectorToXml("pt", scaleToGML(point.location));
ptElement.addChild("t").setFloatContent(point.time);
strokeElement.addChild(ptElement);
}
}
}
saveXML( gml, filepath );
}
/**
* Start recording a new stroke
* Creates a new Stroke and assigns it to currentStroke
* @param brushStyle Brush to apply to this new stroke
*/
void startStroke(Brush brushStyle) {
undos.clear();
if( currentStroke == null ) {
currentStroke = new Stroke( app, brushStyle );
strokes.add( currentStroke );
} else {
System.err.println("Already started stroke. Please endStroke before beginning new one");
}
}
/**
* Start recording a new stroke
* Creates a new Stroke and assigns it to currentStroke
* @param name Name of the Brush
* @param c Color of the Brush
* @param w Weight of the Brush stroke
*/
void startStroke(String n, int c, int w) {
startStroke( new Brush( n, c, w ) );
}
/**
* Start recording a new stroke
* Creates a new Stroke and assigns it to currentStroke
*/
void startStroke() {
startStroke( new Brush() );
}
/**
* End the current stroke
* Sets currentStroke to null
*/
void endStroke() {
currentStroke.createMesh();
currentStroke = null;
}
/**
* Add a point to the current stroke
* @param t Time value for new Point
* @param lx X coordinate of points location.
* @param ly Y coordinate of points location.
* @param lz Z coordinate of points location.
* @param ignoreMinimumDistance If set will record new point even if under minimum distance from last point
*/
void addPoint(float t, float lx, float ly, float lz, boolean ignoreMinimumDistance) {
if( currentStroke != null ) {
float distance = currentStroke.distanceToLast(lx, ly, lz);
//Make sure new points are a minimum distance from other points
if( currentStroke.points.size() == 0 || ignoreMinimumDistance || distance > minimumDistance || distance < 0) {
currentStroke.add( new Point(t, lx, ly, lz) );
}
} else {
//Instead of an error message should it just initiate a new stroke and then add it?
System.err.println("ERROR: No current stroke. Call startStroke before adding new point.");
}
}
/**
* Add a point to the current stroke
* @param t Time value for new Point
* @param lx X coordinate of points location.
* @param ly Y coordinate of points location.
* @param lz Z coordinate of points location.
*/
void addPoint(float t, float lx, float ly, float lz) {
addPoint( t, lx, ly, lz, false);
}
/**
* Add a point to the current stroke
* @param t Time value for new Point
* @param location Vector representing the location of the point
*/
void addPoint(float t, PVector location) {
addPoint( t, location.x, location.y, location.z, false );
}
/**
* Add a point to the current stroke
* @param t Time value for new Point
* @param location Vector representing the location of the point
*/
void addPoint(float t, PVector location, boolean ignoreMinimumDistance) {
addPoint( t, location.x, location.y, location.z, ignoreMinimumDistance );
}
/**
* Creates or recreates a mesh from the stroke data
*/
void createMesh() {
for( Stroke s : strokes ) {
s.createMesh();
}
}
/**
* List strokes ( and points ) of the current drawing
*/
void list() {
for( Stroke stroke : strokes ) {
stroke.list();
}
}
//Display the mesh
/**
* Display the mesh
* Possibly add ability to display a simple path as well
*/
void display() {
for( Stroke stroke : strokes ) {
stroke.display();
}
}
/**
* Reset the Drawing object to the template file
*/
void reset() {
load("template.gml");
}
/**
* Remove all Stroke data from the Drawing object
*/
void clearStrokes() {
strokes.clear();
}
/**
* Removes the last stroke from the Drawing object
*/
void undoLastStroke() {
if( !strokes.isEmpty() ) {
undos.add( strokes.remove(strokes.size() - 1) );
}
}
/**
* Add last stroke removed from Drawing object back.
*/
void redoLastStroke() {
if( !undos.isEmpty() ) {
strokes.add( undos.remove(undos.size() - 1 ) );
}
}
/**
* Setter for minimum distance variable.
*/
void setMinimumDistance(float distance ){
minimumDistance = distance;
}
/**
* Export an STL file of the mesh
* @param filepath Name of the STL file to export to
*/
void export(String filepath) {
}
/**
* Convert an XML node with x, y, z components to a PVector
* @param node Node you want to convert
* @return PVector with values or null if can't find coordinate data
*/
PVector xmlToVector( XML element ) {
if( element != null ) {
XML xElement = element.getChild("x");
XML yElement = element.getChild("y");
XML zElement = element.getChild("z");
float x, y, z;
if( xElement != null && yElement != null ) {
x = xElement.getFloatContent();
y = yElement.getFloatContent();
if( zElement != null ) {
z = zElement.getFloatContent();
} else {
z = 0.0;
}
return new PVector(x, y, z);
} else {
System.err.println("ERROR: Element doesn't contain x or y coordinates.");
return null;
}
} else {
System.err.println("ERROR: Element is null.");
return null;
}
}
/**
* Converts a PVector into an XML element with x, y, z components
* @param name The name of the new element
* @param vector The PVector to convert
* @return A new XML element
*/
XML vectorToXml( String name, PVector vector ) {
if( vector != null ) {
XML newElement = new XML(name);
newElement.addChild("x").setFloatContent(vector.x);
newElement.addChild("y").setFloatContent(vector.y);
newElement.addChild("z").setFloatContent(vector.z);
return newElement;
} else {
System.err.println("ERROR: PVector is null.");
return null;
}
}
/**
* Convert a GML pt element value to screen coordinates for Processing
* PVector to convert
*/
PVector scaleToScreen( PVector vector ) {
vector.sub( new PVector( 0.5, 0.5, 0.5 ) );
PVector scaledVector = new PVector();
//X axis is up
if( abs( up.x ) == 1 ) {
scaledVector.x = vector.y * screenBounds.x;
if( up.x > 0 ) {
scaledVector.y = screenBounds.y - vector.x * screenBounds.y;
} else {
scaledVector.y = vector.x * screenBounds.y;
}
scaledVector.z = vector.z * screenBounds.z;
//Y axis is up
} else if( abs( up.y ) == 1 ) {
scaledVector.x = vector.x * screenBounds.x;
if( up.y > 0 ) {
scaledVector.y = screenBounds.y - vector.y * screenBounds.y;
} else {
scaledVector.y = vector.y * screenBounds.y;
}
scaledVector.z = vector.z * screenBounds.z;
//Z axis is up
} else {
scaledVector.x = vector.x * screenBounds.x;
if( up.z > 0 ) {
scaledVector.y = screenBounds.y - vector.z * screenBounds.y;
} else {
scaledVector.y = vector.z * screenBounds.y;
}
scaledVector.z = vector.y * screenBounds.z;
}
return scaledVector;
}
/**
* Scale screen coordinates to GML (0 to 1) based on screenBounds)
* @param vector Vector to scale
* @return PVector scaled to 1 to 0
*/
PVector scaleToGML( PVector vector ) {
PVector scaledVector = new PVector( vector.x / screenBounds.x, vector.y / screenBounds.y, vector.z / screenBounds.z);
scaledVector.add( new PVector( 0.5, 0.5, 0.5 ) );
return scaledVector;
}
}