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 |