Import_3ds: Improved distance cue node setup
[blender-addons.git] / render_povray / particles.py
blob3d517ade7f0d205ef5864d89452391a764df7283
1 # SPDX-FileCopyrightText: 2021-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 """Get some Blender particle objects translated to POV."""
7 import bpy
9 import random
12 def pixel_relative_guess(ob):
13 """Convert some object x dimension to a rough pixel relative order of magnitude"""
14 from bpy_extras import object_utils
16 scene = bpy.context.scene
17 cam = scene.camera
18 render = scene.render
19 # Get rendered image resolution
20 output_x_res = render.resolution_x
21 focal_length = cam.data.lens
22 # Get object bounding box size
23 object_location = ob.location
24 object_dimension_x = ob.dimensions[0]
25 world_to_camera = object_utils.world_to_camera_view(scene, cam, object_location)
27 apparent_size = (object_dimension_x * focal_length) / world_to_camera[2]
28 sensor_width = cam.data.sensor_width
29 pixel_pitch_x = sensor_width / output_x_res
30 return apparent_size / pixel_pitch_x
33 def export_hair(file, ob, mod, p_sys, global_matrix):
34 """Get Blender path particles (hair strands) objects translated to POV sphere_sweep unions."""
35 # tstart = time.time()
36 from .render import write_matrix
38 textured_hair = 0
39 depsgraph = bpy.context.evaluated_depsgraph_get()
40 p_sys_settings = p_sys.settings.evaluated_get(depsgraph)
41 if ob.material_slots[p_sys_settings.material - 1].material and ob.active_material is not None:
42 pmaterial = ob.material_slots[p_sys_settings.material - 1].material
43 # XXX Todo: replace by pov_(Particles?)_texture_slot
44 for th in pmaterial.pov_texture_slots:
45 povtex = th.texture # slot.name
46 tex = bpy.data.textures[povtex]
48 if (
49 tex
50 and th.use
51 and ((tex.type == "IMAGE" and tex.image) or tex.type != "IMAGE")
52 and th.use_map_color_diffuse
54 textured_hair = 1
55 if pmaterial.strand.use_blender_units:
56 strand_start = pmaterial.strand.root_size
57 strand_end = pmaterial.strand.tip_size
58 else:
59 try:
60 # inexact pixel size, just to make radius relative to screen and object size.
61 pixel_fac = pixel_relative_guess(ob)
62 except ZeroDivisionError:
63 # Fallback to hardwired constant value
64 pixel_fac = 4500
65 print("no pixel size found for stand radius, falling back to %i" % pixel_fac)
67 strand_start = pmaterial.strand.root_size / pixel_fac
68 strand_end = pmaterial.strand.tip_size / pixel_fac
69 strand_shape = pmaterial.strand.shape
70 else:
71 pmaterial = "default" # No material assigned in blender, use default one
72 strand_start = 0.01
73 strand_end = 0.01
74 strand_shape = 0.0
75 # Set the number of particles to render count rather than 3d view display
76 # p_sys.set_resolution(scene, ob, 'RENDER') # DEPRECATED
77 # When you render, the entire dependency graph will be
78 # evaluated at render resolution, including the particles.
79 # In the viewport it will be at viewport resolution.
80 # So there is no need for render engines to use this function anymore,
81 # it's automatic now.
82 steps = p_sys_settings.display_step
83 steps = 2**steps # or + 1 # Formerly : len(particle.hair_keys)
85 total_number_of_strands = p_sys_settings.count * p_sys_settings.rendered_child_count
86 # hairCounter = 0
87 file.write("#declare HairArray = array[%i] {\n" % total_number_of_strands)
88 for pindex in range(total_number_of_strands):
90 # if particle.is_exist and particle.is_visible:
91 # hairCounter += 1
92 # controlPointCounter = 0
93 # Each hair is represented as a separate sphere_sweep in POV-Ray.
95 file.write("sphere_sweep{")
96 if p_sys_settings.use_hair_bspline:
97 file.write("b_spline ")
98 file.write(
99 "%i,\n" % (steps + 2)
100 ) # +2 because the first point needs tripling to be more than a handle in POV
101 else:
102 file.write("linear_spline ")
103 file.write("%i,\n" % steps)
104 # changing world coordinates to object local coordinates by
105 # multiplying with inverted matrix
106 init_coord = ob.matrix_world.inverted() @ (p_sys.co_hair(ob, particle_no=pindex, step=0))
107 init_coord = (init_coord[0], init_coord[1], init_coord[2])
108 if (
109 ob.material_slots[p_sys_settings.material - 1].material
110 and ob.active_material is not None
112 pmaterial = ob.material_slots[p_sys_settings.material - 1].material
113 for th in pmaterial.pov_texture_slots:
114 povtex = th.texture # slot.name
115 tex = bpy.data.textures[povtex]
116 if tex and th.use and th.use_map_color_diffuse:
117 # treat POV textures as bitmaps
118 if (
119 tex.type == "IMAGE"
120 and tex.image
121 and th.texture_coords == "UV"
122 and ob.data.uv_textures is not None
124 # or (
125 # tex.pov.tex_pattern_type != 'emulator'
126 # and th.texture_coords == 'UV'
127 # and ob.data.uv_textures is not None
128 # ):
129 image = tex.image
130 image_width = image.size[0]
131 image_height = image.size[1]
132 image_pixels = image.pixels[:]
133 uv_co = p_sys.uv_on_emitter(mod, p_sys.particles[pindex], pindex, 0)
134 x_co = round(uv_co[0] * (image_width - 1))
135 y_co = round(uv_co[1] * (image_height - 1))
136 pixelnumber = (image_width * y_co) + x_co
137 r = image_pixels[pixelnumber * 4]
138 g = image_pixels[pixelnumber * 4 + 1]
139 b = image_pixels[pixelnumber * 4 + 2]
140 a = image_pixels[pixelnumber * 4 + 3]
141 init_color = (r, g, b, a)
142 else:
143 # only overwrite variable for each competing texture for now
144 init_color = tex.evaluate(init_coord)
145 for step in range(steps):
146 coord = ob.matrix_world.inverted() @ (p_sys.co_hair(ob, particle_no=pindex, step=step))
147 # for controlPoint in particle.hair_keys:
148 if p_sys_settings.clump_factor:
149 hair_strand_diameter = p_sys_settings.clump_factor / 200.0 * random.uniform(0.5, 1)
150 elif step == 0:
151 hair_strand_diameter = strand_start
152 else:
153 # still initialize variable
154 hair_strand_diameter = strand_start
155 if strand_shape == 0.0:
156 fac = step
157 elif strand_shape < 0:
158 fac = pow(step, (1.0 + strand_shape))
159 else:
160 fac = pow(step, (1.0 / (1.0 - strand_shape)))
161 hair_strand_diameter += (
162 fac * (strand_end - strand_start) / (p_sys_settings.display_step + 1)
163 ) # XXX +1 or -1 or nothing ?
164 abs_hair_strand_diameter = abs(hair_strand_diameter)
165 if step == 0 and p_sys_settings.use_hair_bspline:
166 # Write three times the first point to compensate pov Bezier handling
167 file.write(
168 "<%.6g,%.6g,%.6g>,%.7g,\n"
169 % (coord[0], coord[1], coord[2], abs_hair_strand_diameter)
171 file.write(
172 "<%.6g,%.6g,%.6g>,%.7g,\n"
173 % (coord[0], coord[1], coord[2], abs_hair_strand_diameter)
175 # Useless because particle location is the tip, not the root:
176 # file.write(
177 # '<%.6g,%.6g,%.6g>,%.7g'
178 # % (
179 # particle.location[0],
180 # particle.location[1],
181 # particle.location[2],
182 # abs_hair_strand_diameter
185 # file.write(',\n')
186 # controlPointCounter += 1
187 # total_number_of_strands += len(p_sys.particles)# len(particle.hair_keys)
189 # Each control point is written out, along with the radius of the
190 # hair at that point.
191 file.write(
192 "<%.6g,%.6g,%.6g>,%.7g" % (coord[0], coord[1], coord[2], abs_hair_strand_diameter)
195 # All coordinates except the last need a following comma.
197 if step == steps - 1:
198 if textured_hair:
199 # Write pigment and alpha (between Pov and Blender,
200 # alpha 0 and 1 are reversed)
201 file.write(
202 "\npigment{ color srgbf < %.3g, %.3g, %.3g, %.3g> }\n"
203 % (init_color[0], init_color[1], init_color[2], 1.0 - init_color[3])
205 # End the sphere_sweep declaration for this hair
206 file.write("}\n")
208 else:
209 file.write(",\n")
210 # All but the final sphere_sweep (each array element) needs a terminating comma.
211 if pindex != total_number_of_strands:
212 file.write(",\n")
213 else:
214 file.write("\n")
216 # End the array declaration.
218 file.write("}\n")
219 file.write("\n")
221 if not textured_hair:
222 # Pick up the hair material diffuse color and create a default POV-Ray hair texture.
224 file.write("#ifndef (HairTexture)\n")
225 file.write(" #declare HairTexture = texture {\n")
226 file.write(
227 " pigment {srgbt <%s,%s,%s,%s>}\n"
229 pmaterial.diffuse_color[0],
230 pmaterial.diffuse_color[1],
231 pmaterial.diffuse_color[2],
232 (pmaterial.strand.width_fade + 0.05),
235 file.write(" }\n")
236 file.write("#end\n")
237 file.write("\n")
239 # Dynamically create a union of the hairstrands (or a subset of them).
240 # By default use every hairstrand, commented line is for hand tweaking test renders.
241 file.write("//Increasing HairStep divides the amount of hair for test renders.\n")
242 file.write("#ifndef(HairStep) #declare HairStep = 1; #end\n")
243 file.write("union{\n")
244 file.write(" #local I = 0;\n")
245 file.write(" #while (I < %i)\n" % total_number_of_strands)
246 file.write(" object {HairArray[I]")
247 if textured_hair:
248 file.write("\n")
249 else:
250 file.write(" texture{HairTexture}\n")
251 # Translucency of the hair:
252 file.write(" hollow\n")
253 file.write(" double_illuminate\n")
254 file.write(" interior {\n")
255 file.write(" ior 1.45\n")
256 file.write(" media {\n")
257 file.write(" scattering { 1, 10*<0.73, 0.35, 0.15> /*extinction 0*/ }\n")
258 file.write(" absorption 10/<0.83, 0.75, 0.15>\n")
259 file.write(" samples 1\n")
260 file.write(" method 2\n")
261 file.write(" density {cylindrical\n")
262 file.write(" color_map {\n")
263 file.write(" [0.0 rgb <0.83, 0.45, 0.35>]\n")
264 file.write(" [0.5 rgb <0.8, 0.8, 0.4>]\n")
265 file.write(" [1.0 rgb <1,1,1>]\n")
266 file.write(" }\n")
267 file.write(" }\n")
268 file.write(" }\n")
269 file.write(" }\n")
270 file.write(" }\n")
272 file.write(" #local I = I + HairStep;\n")
273 file.write(" #end\n")
275 write_matrix(file, global_matrix @ ob.matrix_world)
277 file.write("}")
278 print("Totals hairstrands written: %i" % total_number_of_strands)
279 print("Number of tufts (particle systems): %i" % len(ob.particle_systems))