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