1if not modules then modules = { } end modules [ ' lpdf-u3d ' ] = {
2 version = 1 . 001 ,
3 comment = " companion to lpdf-ini.mkiv " ,
4 author = " Hans Hagen, PRAGMA-ADE, Hasselt NL " ,
5 copyright = " PRAGMA ADE / ConTeXt Development Team " ,
6 license = " see context related readme files "
7}
8
9
10
11
12
13
14
15
16
17
18
19
20local tonumber = tonumber
21local formatters , find = string . formatters , string . find
22local cos , sin , sqrt , pi , atan2 , abs = math . cos , math . sin , math . sqrt , math . pi , math . atan2 , math . abs
23
24local backends , lpdf = backends , lpdf
25
26local nodeinjections = backends . pdf . nodeinjections
27
28local pdfconstant = lpdf . constant
29local pdfboolean = lpdf . boolean
30local pdfunicode = lpdf . unicode
31local pdfdictionary = lpdf . dictionary
32local pdfarray = lpdf . array
33local pdfnull = lpdf . null
34local pdfreference = lpdf . reference
35local pdfflushstreamobject = lpdf . flushstreamobject
36local pdfflushstreamfileobject = lpdf . flushstreamfileobject
37
38local checkedkey = lpdf . checkedkey
39local limited = lpdf . limited
40
41local embedimage = images . embed
42
43local schemes = table . tohash {
44 " Artwork " , " None " , " White " , " Day " , " Night " , " Hard " ,
45 " Primary " , " Blue " , " Red " , " Cube " , " CAD " , " Headlamp " ,
46}
47
48local modes = table . tohash {
49 " Solid " , " SolidWireframe " , " Transparent " , " TransparentWireframe " , " BoundingBox " ,
50 " TransparentBoundingBox " , " TransparentBoundingBoxOutline " , " Wireframe " ,
51 " ShadedWireframe " , " HiddenWireframe " , " Vertices " , " ShadedVertices " , " Illustration " ,
52 " SolidOutline " , " ShadedIllustration " ,
53}
54
55local function normalize ( x , y , z )
56 local modulo = sqrt ( x * x + y * y + z * z ) ;
57 if modulo ~ = 0 then
58 return x / modulo , y / modulo , z / modulo
59 else
60 return x , y , z
61 end
62end
63
64local function rotate ( vect_x , vect_y , vect_z , tet , axis_x , axis_y , axis_z )
65
66 local c , s = cos ( tet * pi / 180 ) , sin ( tet * pi / 180 )
67 local r = 1 - c
68 local n = sqrt ( axis_x * axis_x + axis_y * axis_y + axis_z * axis_z )
69 axis_x , axis_y , axis_z = axis_x / n , axis_y / n , axis_z / n
70 return
71 ( axis_x * axis_x * r + c ) * vect_x + ( axis_x * axis_y * r - axis_z * s ) * vect_y + ( axis_x * axis_z * r + axis_y * s ) * vect_z ,
72 ( axis_x * axis_y * r + axis_z * s ) * vect_x + ( axis_y * axis_y * r + c ) * vect_y + ( axis_y * axis_z * r - axis_x * s ) * vect_z ,
73 ( axis_x * axis_z * r - axis_y * s ) * vect_x + ( axis_y * axis_z * r + axis_x * s ) * vect_y + ( axis_z * axis_z * r + c ) * vect_z
74end
75
76local function make3dview ( view )
77
78 local name = view . name
79 local name = pdfunicode ( name ~ = " " and name or " unknown view " )
80
81 local viewdict = pdfdictionary {
82 Type = pdfconstant ( " 3DView " ) ,
83 XN = name ,
84 IN = name ,
85 NR = true ,
86 }
87
88 local bg = checkedkey ( view , " bg " , " table " )
89 if bg then
90 viewdict . BG = pdfdictionary {
91 Type = pdfconstant ( " 3DBG " ) ,
92 C = pdfarray { limited ( bg [ 1 ] , 1 , 1 , 1 ) , limited ( bg [ 2 ] , 1 , 1 , 1 ) , limited ( bg [ 3 ] , 1 , 1 , 1 ) } ,
93 }
94 end
95
96 local lights = checkedkey ( view , " lights " , " string " )
97 if lights and schemes [ lights ] then
98 viewdict . LS = pdfdictionary {
99 Type = pdfconstant ( " 3DLightingScheme " ) ,
100 Subtype = pdfconstant ( lights ) ,
101 }
102 end
103
104
105
106 local u3dview = checkedkey ( view , " u3dview " , " string " )
107 if u3dview then
108 viewdict . MS = pdfconstant ( " U3D " )
109 viewdict . U3DPath = u3dview
110 end
111
112
113
114 local c2c = checkedkey ( view , " c2c " , " table " )
115 local coo = checkedkey ( view , " coo " , " table " )
116 local roo = checkedkey ( view , " roo " , " number " )
117 local azimuth = checkedkey ( view , " azimuth " , " number " )
118 local altitude = checkedkey ( view , " altitude " , " number " )
119
120 if c2c or coo or roo or azimuth or altitude then
121
122 local pos = checkedkey ( view , " pos " , " table " )
123 local dir = checkedkey ( view , " dir " , " table " )
124 local upv = checkedkey ( view , " upv " , " table " )
125 local roll = checkedkey ( view , " roll " , " table " )
126
127 local coo_x , coo_y , coo_z = 0 , 0 , 0
128 local dir_x , dir_y , dir_z = 0 , 0 , 0
129 local trans_x , trans_y , trans_z = 0 , 0 , 0
130 local left_x , left_y , left_z = 0 , 0 , 0
131 local up_x , up_y , up_z = 0 , 0 , 0
132
133
134
135 if coo then
136 coo_x , coo_y , coo_z = tonumber ( coo [ 1 ] ) or 0 , tonumber ( coo [ 2 ] ) or 0 , tonumber ( coo [ 3 ] ) or 0
137 end
138
139
140
141 if roo then
142 roo = abs ( roo )
143 end
144 if not roo or roo = = 0 then
145 roo = 0 . 000000000000000001
146 end
147
148
149
150 if pos then
151 dir_x = coo_x - ( tonumber ( pos [ 1 ] ) or 0 )
152 dir_y = coo_y - ( tonumber ( pos [ 2 ] ) or 0 )
153 dir_z = coo_z - ( tonumber ( pos [ 3 ] ) or 0 )
154 if not roo then
155 roo = sqrt ( dir_x * dir_x + dir_y * dir_y + dir_z * dir_z )
156 end
157 if dir_x = = 0 and dir_y = = 0 and dir_z = = 0 then dir_y = 1 end
158 dir_x , dir_y , dir_z = normalize ( dir_x , dir_y , dir_z )
159 end
160
161
162
163 if dir then
164 dir_x , dir_y , dir_z = tonumber ( dir [ 1 ] or 0 ) , tonumber ( dir [ 2 ] or 0 ) , tonumber ( dir [ 3 ] or 0 )
165 if dir_x = = 0 and dir_y = = 0 and dir_z = = 0 then dir_y = 1 end
166 dir_x , dir_y , dir_z = normalize ( dir_x , dir_y , dir_z )
167 end
168
169
170
171 if c2c then
172 dir_x , dir_y , dir_z = - tonumber ( c2c [ 1 ] or 0 ) , - tonumber ( c2c [ 2 ] or 0 ) , - tonumber ( c2c [ 3 ] or 0 )
173 if dir_x = = 0 and dir_y = = 0 and dir_z = = 0 then dir_y = 1 end
174 dir_x , dir_y , dir_z = normalize ( dir_x , dir_y , dir_z )
175 end
176
177
178
179 if altitude or azimuth then
180 dir_x , dir_y , dir_z = -1 , 0 , 0
181 if altitude then dir_x , dir_y , dir_z = rotate ( dir_x , dir_y , dir_z , - altitude , 0 , 1 , 0 ) end
182 if azimuth then dir_x , dir_y , dir_z = rotate ( dir_x , dir_y , dir_z , azimuth , 0 , 0 , 1 ) end
183 end
184
185
186
187 if rot then
188 if dir_x = = 0 and dir_y = = 0 and dir_z = = 0 then dir_z = -1 end
189 dir_x , dir_y , dir_z = rotate ( dir_x , dir_y , dir_z , tonumber ( rot [ 1 ] ) or 0 , 1 , 0 , 0 )
190 dir_x , dir_y , dir_z = rotate ( dir_x , dir_y , dir_z , tonumber ( rot [ 2 ] ) or 0 , 0 , 1 , 0 )
191 dir_x , dir_y , dir_z = rotate ( dir_x , dir_y , dir_z , tonumber ( rot [ 3 ] ) or 0 , 0 , 0 , 1 )
192 end
193
194
195
196 if dir_x = = 0 and dir_y = = 0 and dir_z = = 0 then dir_y = 1 end
197
198
199
200
201 if upv then
202 up_x , up_y , up_z = tonumber ( upv [ 1 ] ) or 0 , tonumber ( upv [ 2 ] ) or 0 , tonumber ( upv [ 3 ] ) or 0
203 else
204
205 if abs ( dir_x ) = = 0 and abs ( dir_y ) = = 0 then
206 if dir_z < 0 then
207 up_y = 1
208 else
209 up_y = -1
210 end
211 else
212
213 up_x , up_y , up_z = - dir_z * dir_x , - dir_z * dir_y , - dir_z * dir_z + 1
214 end
215 end
216
217
218
219 up_x , up_y , up_z = normalize ( up_x , up_y , up_z )
220
221
222
223 left_x , left_y , left_z = dir_z * up_y - dir_y * up_z , dir_x * up_z - dir_z * up_x , dir_y * up_x - dir_x * up_y
224
225
226
227 left_x , left_y , left_z = normalize ( left_x , left_y , left_z )
228
229
230
231 if roll then
232 local sinroll = sin ( ( roll / 180 . 0 ) * pi )
233 local cosroll = cos ( ( roll / 180 . 0 ) * pi )
234 left_x = left_x * cosroll + up_x * sinroll
235 left_y = left_y * cosroll + up_y * sinroll
236 left_z = left_z * cosroll + up_z * sinroll
237 up_x = up_x * cosroll + left_x * sinroll
238 up_y = up_y * cosroll + left_y * sinroll
239 up_z = up_z * cosroll + left_z * sinroll
240 end
241
242
243
244 trans_x , trans_y , trans_z = coo_x - roo * dir_x , coo_y - roo * dir_y , coo_z - roo * dir_z
245
246 viewdict . MS = pdfconstant ( " M " )
247 viewdict . CO = roo
248 viewdict . C2W = pdfarray {
249 left_x , left_y , left_z ,
250 up_x , up_y , up_z ,
251 dir_x , dir_y , dir_z ,
252 trans_x , trans_y , trans_z ,
253 }
254
255 end
256
257 local aac = tonumber ( view . aac )
258 local mag = tonumber ( view . mag )
259
260 if aac and aac > 0 and aac < 180 then
261 viewdict . P = pdfdictionary {
262 Subtype = pdfconstant ( " P " ) ,
263 PS = pdfconstant ( " Min " ) ,
264 FOV = aac ,
265 }
266 elseif mag and mag > 0 then
267 viewdict . P = pdfdictionary {
268 Subtype = pdfconstant ( " O " ) ,
269 OS = mag ,
270 }
271 end
272
273 local mode = modes [ view . rendermode ]
274 if mode then
275 pdfdictionary {
276 Type = pdfconstant ( " 3DRenderMode " ) ,
277 Subtype = pdfconstant ( mode ) ,
278 }
279 end
280
281
282
283 local crosssection = checkedkey ( view , " crosssection " , " table " )
284 if crosssection then
285 local crossdict = pdfdictionary {
286 Type = pdfconstant ( " 3DCrossSection " )
287 }
288
289 local c = checkedkey ( crosssection , " point " , " table " ) or checkedkey ( crosssection , " center " , " table " )
290 if c then
291 crossdict . C = pdfarray { tonumber ( c [ 1 ] ) or 0 , tonumber ( c [ 2 ] ) or 0 , tonumber ( c [ 3 ] ) or 0 }
292 end
293
294 local normal = checkedkey ( crosssection , " normal " , " table " )
295 if normal then
296 local x , y , z = tonumber ( normal [ 1 ] or 0 ) , tonumber ( normal [ 2 ] or 0 ) , tonumber ( normal [ 3 ] or 0 )
297 if sqrt ( x * x + y * y + z * z ) = = 0 then
298 x , y , z = 1 , 0 , 0
299 end
300 crossdict . O = pdfarray {
301 pdfnull ,
302 atan2 ( - z , sqrt ( x * x + y * y ) ) * 180 / pi ,
303 atan2 ( y , x ) * 180 / pi ,
304 }
305 end
306
307 local orient = checkedkey ( crosssection , " orient " , " table " )
308 if orient then
309 crossdict . O = pdfarray {
310 tonumber ( orient [ 1 ] ) or 1 ,
311 tonumber ( orient [ 2 ] ) or 0 ,
312 tonumber ( orient [ 3 ] ) or 0 ,
313 }
314 end
315
316 crossdict . IV = cross . intersection or false
317 crossdict . ST = cross . transparent or false
318
319 viewdict . SA = next ( crossdict ) and pdfarray { crossdict }
320 end
321
322 local nodes = checkedkey ( view , " nodes " , " table " )
323 if nodes then
324 local nodelist = pdfarray ( )
325 for i = 1 , # nodes do
326 local node = checkedkey ( nodes , i , " table " )
327 if node then
328 local position = checkedkey ( node , " position " , " table " )
329 nodelist [ # nodelist + 1 ] = pdfdictionary {
330 Type = pdfconstant ( " 3DNode " ) ,
331 N = node . name or ( " node_ " . . i ) ,
332 M = position and # position = = 12 and pdfarray ( position ) ,
333 V = node . visible or true ,
334 O = node . opacity or 0 ,
335 RM = pdfdictionary {
336 Type = pdfconstant ( " 3DRenderMode " ) ,
337 Subtype = pdfconstant ( node . rendermode or " Solid " ) ,
338 } ,
339 }
340 end
341 end
342 viewdict . NA = nodelist
343 end
344
345 return viewdict
346
347end
348
349local stored_js , stored_3d , stored_pr , streams = { } , { } , { } , { }
350
351local f_image = formatters [ " q /GS gs %.6N 0 0 %.6N 0 0 cm /IM Do Q " ]
352
353local function insert3d ( spec )
354
355 local width , height , factor = spec . width , spec . height , spec . factor or number . dimenfactors . bp
356 local display , controls , label , foundname = spec . display , spec . controls , spec . label , spec . foundname
357
358 local param = ( display and parametersets [ display ] ) or { }
359 local streamparam = ( controls and parametersets [ controls ] ) or { }
360 local name = " 3D Artwork " . . ( param . name or label or " Unknown " )
361
362 local activationdict = pdfdictionary {
363 TB = pdfboolean ( param . toolbar , true ) ,
364 NP = pdfboolean ( param . tree , false ) ,
365 }
366
367 local stream = streams [ label ]
368 if not stream then
369
370 local subtype , subdata = " U3D " , io . loaddata ( foundname ) or " "
371 if find ( subdata , " ^PRC " ) then
372 subtype = " PRC "
373 elseif find ( subdata , " ^U3D " ) then
374 subtype = " U3D "
375 elseif file . suffix ( foundname ) = = " prc " then
376 subtype = " PRC "
377 end
378
379 local attr = pdfdictionary {
380 Type = pdfconstant ( " 3D " ) ,
381 Subtype = pdfconstant ( subtype ) ,
382 }
383 local streamviews = checkedkey ( streamparam , " views " , " table " )
384 if streamviews then
385 local list = pdfarray ( )
386 for i = 1 , # streamviews do
387 local v = checkedkey ( streamviews , i , " table " )
388 if v then
389 list [ # list + 1 ] = make3dview ( v )
390 end
391 end
392 attr . VA = list
393 end
394 if checkedkey ( streamparam , " view " , " table " ) then
395 attr . DV = make3dview ( streamparam . view )
396 elseif checkedkey ( streamparam , " view " , " string " ) then
397 attr . DV = streamparam . view
398 end
399 local js = checkedkey ( streamparam , " js " , " string " )
400 if js then
401 local jsref = stored_js [ js ]
402 if not jsref then
403 jsref = pdfflushstreamfileobject ( js )
404 stored_js [ js ] = jsref
405 end
406 attr . OnInstantiate = pdfreference ( jsref )
407 end
408 stored_3d [ label ] = pdfflushstreamfileobject ( foundname , attr )
409 stream = 1
410 else
411 stream = stream + 1
412 end
413 streams [ label ] = stream
414
415 local name = pdfunicode ( name )
416
417 local annot = pdfdictionary {
418 Subtype = pdfconstant ( " 3D " ) ,
419 T = name ,
420 Contents = name ,
421 NM = name ,
422 [ " 3DD " ] = pdfreference ( stored_3d [ label ] ) ,
423 [ " 3DA " ] = activationdict ,
424 }
425 if checkedkey ( param , " view " , " table " ) then
426 annot [ " 3DV " ] = make3dview ( param . view )
427 elseif checkedkey ( param , " view " , " string " ) then
428 annot [ " 3DV " ] = param . view
429 end
430
431 local preview = checkedkey ( param , " preview " , " string " )
432 if preview then
433 activationdict . A = pdfconstant ( " XA " )
434 local tag = formatters [ " %s:%s:%s " ] ( label , stream , preview )
435 local ref = stored_pr [ tag ]
436 if not ref then
437 local figure = embedimage {
438 filename = preview ,
439 width = width ,
440 height = height
441 }
442 ref = figure . objnum
443 stored_pr [ tag ] = ref
444 end
445 if ref then
446 local pw = pdfdictionary {
447 Type = pdfconstant ( " XObject " ) ,
448 Subtype = pdfconstant ( " Form " ) ,
449 FormType = 1 ,
450 BBox = pdfarray { 0 , 0 , pdfnumber ( factor * width ) , pdfnumber ( factor * height ) } ,
451 Matrix = pdfarray { 1 , 0 , 0 , 1 , 0 , 0 } ,
452 ProcSet = lpdf . procset ( ) ,
453 Resources = pdfdictionary {
454 XObject = pdfdictionary {
455 IM = pdfreference ( ref )
456 }
457 } ,
458 ExtGState = pdfdictionary {
459 GS = pdfdictionary {
460 Type = pdfconstant ( " ExtGState " ) ,
461 CA = 1 ,
462 ca = 1 ,
463 }
464 } ,
465 }
466 local pwd = pdfflushstreamobject ( f_image ( factor * width , factor * height ) , pw )
467 annot . AP = pdfdictionary {
468 N = pdfreference ( pwd )
469 }
470 end
471 return annot , figure , ref
472 else
473 activationdict . A = pdfconstant ( " PV " )
474 return annot , nil , nil
475 end
476end
477
478function nodeinjections . insertu3d ( spec )
479 local annotation , preview , ref = insert3d {
480 foundname = spec . foundname ,
481 width = spec . width ,
482 height = spec . height ,
483 factor = spec . factor ,
484 display = spec . display ,
485 controls = spec . controls ,
486 label = spec . label ,
487 }
488 node . write ( nodeinjections . annotation ( spec . width , spec . height , 0 , annotation ( ) ) )
489end
490 |