1# Copyright 2018-2019 The glTF-Blender-IO authors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import bpy
16
17from .gltf2_blender_image import BlenderImage
18from ..com.gltf2_blender_conversion import texture_transform_gltf_to_blender
19from io_scene_gltf2.io.com.gltf2_io import Sampler
20from io_scene_gltf2.io.com.gltf2_io_constants import TextureFilter, TextureWrap
21
22def texture(
23    mh,
24    tex_info,
25    location, # Upper-right corner of the TexImage node
26    label, # Label for the TexImg node
27    color_socket,
28    alpha_socket=None,
29    is_data=False,
30):
31    """Creates nodes for a TextureInfo and hooks up the color/alpha outputs."""
32    x, y = location
33    pytexture = mh.gltf.data.textures[tex_info.index]
34    if pytexture.sampler is not None:
35        pysampler = mh.gltf.data.samplers[pytexture.sampler]
36    else:
37        pysampler = Sampler.from_dict({})
38
39    needs_uv_map = False  # whether to create UVMap node
40
41    # Image Texture
42    tex_img = mh.node_tree.nodes.new('ShaderNodeTexImage')
43    tex_img.location = x - 240, y
44    tex_img.label = label
45    # Get image
46    if pytexture.source is not None:
47        BlenderImage.create(mh.gltf, pytexture.source)
48        pyimg = mh.gltf.data.images[pytexture.source]
49        blender_image_name = pyimg.blender_image_name
50        if blender_image_name:
51            tex_img.image = bpy.data.images[blender_image_name]
52    # Set colorspace for data images
53    if is_data:
54        if tex_img.image:
55            tex_img.image.colorspace_settings.is_data = True
56    # Set filtering
57    set_filtering(tex_img, pysampler)
58    # Outputs
59    mh.node_tree.links.new(color_socket, tex_img.outputs['Color'])
60    if alpha_socket is not None:
61        mh.node_tree.links.new(alpha_socket, tex_img.outputs['Alpha'])
62    # Inputs
63    uv_socket = tex_img.inputs[0]
64
65    x -= 340
66
67    # Do wrapping
68    wrap_s = pysampler.wrap_s
69    wrap_t = pysampler.wrap_t
70    if wrap_s is None:
71        wrap_s = TextureWrap.Repeat
72    if wrap_t is None:
73        wrap_t = TextureWrap.Repeat
74    # If wrapping is REPEATxREPEAT or CLAMPxCLAMP, just set tex_img.extension
75    if (wrap_s, wrap_t) == (TextureWrap.Repeat, TextureWrap.Repeat):
76        tex_img.extension = 'REPEAT'
77    elif (wrap_s, wrap_t) == (TextureWrap.ClampToEdge, TextureWrap.ClampToEdge):
78        tex_img.extension = 'EXTEND'
79    else:
80        # Otherwise separate the UV components and use math nodes to compute
81        # the wrapped UV coordinates
82        # => [Separate XYZ] => [Wrap for S] => [Combine XYZ] =>
83        #                   => [Wrap for T] =>
84
85        tex_img.extension = 'EXTEND'  # slightly better errors near the edge than REPEAT
86
87        # Combine XYZ
88        com_uv = mh.node_tree.nodes.new('ShaderNodeCombineXYZ')
89        com_uv.location = x - 140, y - 100
90        mh.node_tree.links.new(uv_socket, com_uv.outputs[0])
91        u_socket = com_uv.inputs[0]
92        v_socket = com_uv.inputs[1]
93        x -= 200
94
95        for i in [0, 1]:
96            wrap = [wrap_s, wrap_t][i]
97            socket = [u_socket, v_socket][i]
98            if wrap == TextureWrap.Repeat:
99                # WRAP node for REPEAT
100                math = mh.node_tree.nodes.new('ShaderNodeMath')
101                math.location = x - 140, y + 30 - i*200
102                math.operation = 'WRAP'
103                math.inputs[1].default_value = 0
104                math.inputs[2].default_value = 1
105                mh.node_tree.links.new(socket, math.outputs[0])
106                socket = math.inputs[0]
107            elif wrap == TextureWrap.MirroredRepeat:
108                # PINGPONG node for MIRRORED_REPEAT
109                math = mh.node_tree.nodes.new('ShaderNodeMath')
110                math.location = x - 140, y + 30 - i*200
111                math.operation = 'PINGPONG'
112                math.inputs[1].default_value = 1
113                mh.node_tree.links.new(socket, math.outputs[0])
114                socket = math.inputs[0]
115            else:
116                # Pass-through CLAMP since the tex_img node is set to EXTEND
117                pass
118            if i == 0:
119                u_socket = socket
120            else:
121                v_socket = socket
122        x -= 200
123
124        # Separate XYZ
125        sep_uv = mh.node_tree.nodes.new('ShaderNodeSeparateXYZ')
126        sep_uv.location = x - 140, y - 100
127        mh.node_tree.links.new(u_socket, sep_uv.outputs[0])
128        mh.node_tree.links.new(v_socket, sep_uv.outputs[1])
129        uv_socket = sep_uv.inputs[0]
130        x -= 200
131
132        needs_uv_map = True
133
134    # UV Transform (for KHR_texture_transform)
135    needs_tex_transform = 'KHR_texture_transform' in (tex_info.extensions or {})
136    if needs_tex_transform:
137        mapping = mh.node_tree.nodes.new('ShaderNodeMapping')
138        mapping.location = x - 160, y + 30
139        mapping.vector_type = 'POINT'
140        # Outputs
141        mh.node_tree.links.new(uv_socket, mapping.outputs[0])
142        # Inputs
143        uv_socket = mapping.inputs[0]
144
145        transform = tex_info.extensions['KHR_texture_transform']
146        transform = texture_transform_gltf_to_blender(transform)
147        mapping.inputs['Location'].default_value[0] = transform['offset'][0]
148        mapping.inputs['Location'].default_value[1] = transform['offset'][1]
149        mapping.inputs['Rotation'].default_value[2] = transform['rotation']
150        mapping.inputs['Scale'].default_value[0] = transform['scale'][0]
151        mapping.inputs['Scale'].default_value[1] = transform['scale'][1]
152
153        x -= 260
154        needs_uv_map = True
155
156    # UV Map
157    uv_idx = tex_info.tex_coord or 0
158    try:
159        uv_idx = tex_info.extensions['KHR_texture_transform']['texCoord']
160    except Exception:
161        pass
162    if uv_idx != 0 or needs_uv_map:
163        uv_map = mh.node_tree.nodes.new('ShaderNodeUVMap')
164        uv_map.location = x - 160, y - 70
165        uv_map.uv_map = 'UVMap' if uv_idx == 0 else 'UVMap.%03d' % uv_idx
166        # Outputs
167        mh.node_tree.links.new(uv_socket, uv_map.outputs[0])
168
169def set_filtering(tex_img, pysampler):
170    """Set the filtering/interpolation on an Image Texture from the glTf sampler."""
171    minf = pysampler.min_filter
172    magf = pysampler.mag_filter
173
174    # Ignore mipmapping
175    if minf in [TextureFilter.NearestMipmapNearest, TextureFilter.NearestMipmapLinear]:
176        minf = TextureFilter.Nearest
177    elif minf in [TextureFilter.LinearMipmapNearest, TextureFilter.LinearMipmapLinear]:
178        minf = TextureFilter.Linear
179
180    # If both are nearest or the only specified one was nearest, use nearest.
181    if (minf, magf) in [
182        (TextureFilter.Nearest, TextureFilter.Nearest),
183        (TextureFilter.Nearest, None),
184        (None, TextureFilter.Nearest),
185    ]:
186        tex_img.interpolation = 'Closest'
187    else:
188        tex_img.interpolation = 'Linear'
189