2014-02-10 02:10:30 +01:00
|
|
|
/*************************************************************************/
|
|
|
|
/* rasterizer.cpp */
|
|
|
|
/*************************************************************************/
|
|
|
|
/* This file is part of: */
|
|
|
|
/* GODOT ENGINE */
|
|
|
|
/* http://www.godotengine.org */
|
|
|
|
/*************************************************************************/
|
2017-01-01 22:01:57 +01:00
|
|
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
2014-02-10 02:10:30 +01:00
|
|
|
/* */
|
|
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
|
|
/* a copy of this software and associated documentation files (the */
|
|
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
|
|
/* the following conditions: */
|
|
|
|
/* */
|
|
|
|
/* The above copyright notice and this permission notice shall be */
|
|
|
|
/* included in all copies or substantial portions of the Software. */
|
|
|
|
/* */
|
|
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
/*************************************************************************/
|
|
|
|
#include "rasterizer.h"
|
|
|
|
#include "os/os.h"
|
2017-03-19 00:36:26 +01:00
|
|
|
#include "print_string.h"
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
RID Rasterizer::create_default_material() {
|
|
|
|
|
|
|
|
return material_create();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Fixed MAterial SHADER API */
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
RID Rasterizer::_create_shader(const FixedMaterialShaderKey &p_key) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
ERR_FAIL_COND_V(!p_key.valid, RID());
|
|
|
|
Map<FixedMaterialShaderKey, FixedMaterialShader>::Element *E = fixed_material_shaders.find(p_key);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
if (E) {
|
|
|
|
E->get().refcount++;
|
|
|
|
return E->get().shader;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t t = OS::get_singleton()->get_ticks_usec();
|
|
|
|
|
|
|
|
FixedMaterialShader fms;
|
2017-03-19 00:36:26 +01:00
|
|
|
fms.refcount = 1;
|
|
|
|
fms.shader = shader_create();
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
//create shader code
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
int texcoords_used = 0;
|
2014-02-10 02:10:30 +01:00
|
|
|
String code;
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
static const char *_uv_str[4] = { "UV", "uv_xform", "UV2", "uv_sphere" };
|
|
|
|
#define _TEXUVSTR(m_idx) String(_uv_str[(p_key.texcoord_mask >> (m_idx * 2)) & 0x3])
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
if (p_key.use_pointsize) {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "UV=POINT_COORD;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int i = 0; i < VS::FIXED_MATERIAL_PARAM_MAX; i++) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_key.texture_mask & (1 << i))
|
|
|
|
texcoords_used |= (1 << ((p_key.texcoord_mask >> (i * 2)) & 0x3));
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (texcoords_used & (1 << VS::FIXED_MATERIAL_TEXCOORD_UV_TRANSFORM)) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "uniform mat4 fmp_uv_xform;\n";
|
|
|
|
code += "vec2 uv_xform = (fmp_uv_xform * vec4(UV,0,1)).xy;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* HANDLE NORMAL MAPPING */
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_key.texture_mask & (1 << VS::FIXED_MATERIAL_PARAM_NORMAL)) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
String scode;
|
2017-03-19 00:36:26 +01:00
|
|
|
scode += "uniform float fmp_normal;\n";
|
|
|
|
scode += "uniform texture fmp_normal_tex;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
String uv_str;
|
2017-03-19 00:36:26 +01:00
|
|
|
if (((p_key.texcoord_mask >> (VS::FIXED_MATERIAL_PARAM_NORMAL * 2)) & 0x3) == VS::FIXED_MATERIAL_TEXCOORD_SPHERE) {
|
|
|
|
uv_str = "uv"; //sorry not supported
|
2014-02-10 02:10:30 +01:00
|
|
|
} else {
|
2017-03-19 00:36:26 +01:00
|
|
|
uv_str = _TEXUVSTR(VS::FIXED_MATERIAL_PARAM_NORMAL);
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
2014-10-03 13:58:41 +02:00
|
|
|
if (p_key.use_xy_normalmap) {
|
2017-03-19 00:36:26 +01:00
|
|
|
scode += "vec2 ywnormal=tex( fmp_normal_tex," + uv_str + ").wy * vec2(2.0,2.0) - vec2(1.0,1.0);\n";
|
|
|
|
scode += "NORMALMAP=vec3(ywnormal,sqrt(1 - (ywnormal.x * ywnormal.x) - (ywnormal.y * ywnormal.y) ));\n";
|
2014-10-03 13:58:41 +02:00
|
|
|
} else {
|
2017-03-19 00:36:26 +01:00
|
|
|
scode += "NORMALMAP=tex( fmp_normal_tex," + uv_str + ").xyz * vec3(2.0,2.0,1.0) - vec3(1.0,1.0,0.0);\n";
|
2014-10-03 13:58:41 +02:00
|
|
|
}
|
2017-03-19 00:36:26 +01:00
|
|
|
scode += "NORMALMAP_DEPTH=fmp_normal;\n";
|
2014-10-15 00:44:41 +02:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += scode;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//handle sphere uv if used, do it here because it needs the normal, which may be transformed by a normal map
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (texcoords_used & (1 << VS::FIXED_MATERIAL_TEXCOORD_SPHERE)) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
String tcode;
|
2017-03-19 00:36:26 +01:00
|
|
|
tcode = "vec3 eye_normal = normalize(VERTEX);\n";
|
|
|
|
tcode += "vec3 ref = (eye_normal - 2.0*dot(NORMAL, eye_normal)*NORMAL);\n";
|
|
|
|
tcode += "ref.z+=1.0;\n";
|
|
|
|
tcode += "vec2 uv_sphere = ref.xy*vec2(0.5,0.0-0.5)+vec2(0.5,0.0-0.5);\n";
|
|
|
|
code += tcode;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* HANDLE DIFFUSE LIGHTING */
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "uniform color fmp_diffuse;\n";
|
|
|
|
code += "color diffuse=fmp_diffuse;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
if (p_key.use_color_array)
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "diffuse*=COLOR;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_key.texture_mask & (1 << VS::FIXED_MATERIAL_PARAM_DIFFUSE)) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "uniform texture fmp_diffuse_tex;\n";
|
|
|
|
code += "diffuse*=tex( fmp_diffuse_tex," + _TEXUVSTR(VS::FIXED_MATERIAL_PARAM_DIFFUSE) + ");\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_key.texture_mask & (1 << VS::FIXED_MATERIAL_PARAM_DETAIL)) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
String dcode;
|
2017-03-19 00:36:26 +01:00
|
|
|
dcode += "uniform texture fmp_detail_tex;\n";
|
|
|
|
dcode += "uniform float fmp_detail;\n";
|
|
|
|
dcode += "color detail=tex( fmp_detail_tex," + _TEXUVSTR(VS::FIXED_MATERIAL_PARAM_DETAIL) + ");\n";
|
2014-06-28 04:21:45 +02:00
|
|
|
//aways mix
|
2017-03-19 00:36:26 +01:00
|
|
|
dcode += "diffuse=vec4(mix(diffuse.rgb,detail.rgb,detail.a*fmp_detail),diffuse.a);\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += dcode;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (p_key.use_alpha) {
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "DIFFUSE_ALPHA=diffuse;\n";
|
2014-05-29 15:56:39 +02:00
|
|
|
if (p_key.discard_alpha) {
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "DISCARD=diffuse.a<0.5;\n";
|
2014-05-29 15:56:39 +02:00
|
|
|
}
|
2014-02-10 02:10:30 +01:00
|
|
|
} else {
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "DIFFUSE=diffuse.rgb;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* HANDLE SPECULAR LIGHTING */
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "uniform color fmp_specular;\n";
|
|
|
|
code += "color specular=fmp_specular;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_key.texture_mask & (1 << VS::FIXED_MATERIAL_PARAM_SPECULAR)) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
String scode;
|
2017-03-19 00:36:26 +01:00
|
|
|
scode += "uniform texture fmp_specular_tex;\n";
|
|
|
|
scode += "specular*=tex( fmp_specular_tex," + _TEXUVSTR(VS::FIXED_MATERIAL_PARAM_SPECULAR) + ");\n";
|
|
|
|
code += scode;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "SPECULAR=specular.rgb;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "uniform float fmp_specular_exp;\n";
|
|
|
|
code += "float specular_exp=fmp_specular_exp;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_key.texture_mask & (1 << VS::FIXED_MATERIAL_PARAM_SPECULAR_EXP)) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
String scode;
|
2017-03-19 00:36:26 +01:00
|
|
|
scode += "uniform texture fmp_specular_exp_tex;\n";
|
|
|
|
scode += "specular_exp*=tex( fmp_specular_exp_tex," + _TEXUVSTR(VS::FIXED_MATERIAL_PARAM_SPECULAR_EXP) + ").r;\n";
|
|
|
|
code += scode;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "SPEC_EXP=specular_exp;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
/* HANDLE EMISSION LIGHTING */
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "uniform color fmp_emission;\n";
|
|
|
|
code += "color emission=fmp_emission;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_key.texture_mask & (1 << VS::FIXED_MATERIAL_PARAM_EMISSION)) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
String scode;
|
2017-03-19 00:36:26 +01:00
|
|
|
scode += "uniform texture fmp_emission_tex;\n";
|
|
|
|
scode += "emission*=tex( fmp_emission_tex," + _TEXUVSTR(VS::FIXED_MATERIAL_PARAM_EMISSION) + ");\n";
|
|
|
|
code += scode;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "EMISSION=emission.rgb;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
/* HANDLE GLOW */
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "uniform float fmp_glow;\n";
|
|
|
|
code += "float glow=fmp_glow;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_key.texture_mask & (1 << VS::FIXED_MATERIAL_PARAM_GLOW)) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
String scode;
|
2017-03-19 00:36:26 +01:00
|
|
|
scode += "uniform texture fmp_glow_tex;\n";
|
|
|
|
scode += "glow*=tex( fmp_glow_tex," + _TEXUVSTR(VS::FIXED_MATERIAL_PARAM_GLOW) + ").r;\n";
|
|
|
|
code += scode;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
code += "GLOW=glow;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_key.texture_mask & (1 << VS::FIXED_MATERIAL_PARAM_SHADE_PARAM)) {
|
2014-06-28 04:21:45 +02:00
|
|
|
|
|
|
|
String scode;
|
2017-03-19 00:36:26 +01:00
|
|
|
scode += "uniform texture fmp_shade_param_tex;\n";
|
|
|
|
scode += "SHADE_PARAM=tex( fmp_shade_param_tex," + _TEXUVSTR(VS::FIXED_MATERIAL_PARAM_SHADE_PARAM) + ").r;\n";
|
|
|
|
code += scode;
|
2014-06-28 04:21:45 +02:00
|
|
|
} else {
|
|
|
|
|
|
|
|
String scode;
|
2017-03-19 00:36:26 +01:00
|
|
|
scode += "uniform float fmp_shade_param;\n";
|
|
|
|
scode += "SHADE_PARAM=fmp_shade_param;\n";
|
|
|
|
code += scode;
|
2014-06-28 04:21:45 +02:00
|
|
|
}
|
|
|
|
|
2014-02-10 02:10:30 +01:00
|
|
|
//print_line("**FRAGMENT SHADER GENERATED code: \n"+code);
|
|
|
|
|
|
|
|
String vcode;
|
2017-03-19 00:36:26 +01:00
|
|
|
vcode = "uniform float " + _fixed_material_param_names[VS::FIXED_MATERIAL_PARAM_SPECULAR_EXP] + ";\n";
|
|
|
|
vcode += "SPEC_EXP=" + _fixed_material_param_names[VS::FIXED_MATERIAL_PARAM_SPECULAR_EXP] + ";\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
if (p_key.use_pointsize) {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
vcode += "uniform float " + _fixed_material_point_size_name + ";\n";
|
|
|
|
vcode += "POINT_SIZE=" + _fixed_material_point_size_name + ";\n";
|
|
|
|
// vcode+="POINT_SIZE=10.0;\n";
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2014-06-28 04:21:45 +02:00
|
|
|
String lcode;
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
switch (p_key.light_shader) {
|
2014-06-28 04:21:45 +02:00
|
|
|
|
|
|
|
case VS::FIXED_MATERIAL_LIGHT_SHADER_LAMBERT: {
|
|
|
|
//do nothing
|
|
|
|
|
|
|
|
} break;
|
|
|
|
case VS::FIXED_MATERIAL_LIGHT_SHADER_WRAP: {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
lcode += "float NdotL = max(0.0,((dot( NORMAL, LIGHT_DIR )+SHADE_PARAM)/(1.0+SHADE_PARAM)));";
|
|
|
|
lcode += "vec3 half_vec = normalize(LIGHT_DIR + EYE_VEC);";
|
|
|
|
lcode += "float eye_light = max(dot(NORMAL, half_vec),0.0);";
|
|
|
|
lcode += "LIGHT = LIGHT_DIFFUSE * DIFFUSE * NdotL;";
|
|
|
|
lcode += "if (NdotL > 0.0) {";
|
|
|
|
lcode += "\tLIGHT+=LIGHT_SPECULAR * SPECULAR * pow( eye_light, SPECULAR_EXP );";
|
|
|
|
lcode += "};";
|
2014-06-28 04:21:45 +02:00
|
|
|
|
|
|
|
} break;
|
|
|
|
case VS::FIXED_MATERIAL_LIGHT_SHADER_VELVET: {
|
2017-03-19 00:36:26 +01:00
|
|
|
lcode += "float NdotL = max(0.0,dot( NORMAL, LIGHT_DIR ));";
|
|
|
|
lcode += "vec3 half_vec = normalize(LIGHT_DIR + EYE_VEC);";
|
|
|
|
lcode += "float eye_light = max(dot(NORMAL, half_vec),0.0);";
|
|
|
|
lcode += "LIGHT = LIGHT_DIFFUSE * DIFFUSE * NdotL;";
|
|
|
|
lcode += "float rim = (1.0-abs(dot(NORMAL,vec3(0,0,1))))*SHADE_PARAM;";
|
|
|
|
lcode += "LIGHT += LIGHT_DIFFUSE * DIFFUSE * rim;";
|
|
|
|
lcode += "if (NdotL > 0.0) {";
|
|
|
|
lcode += "\tLIGHT+=LIGHT_SPECULAR * SPECULAR * pow( eye_light, SPECULAR_EXP );";
|
|
|
|
lcode += "};";
|
2014-06-28 04:21:45 +02:00
|
|
|
|
|
|
|
} break;
|
|
|
|
case VS::FIXED_MATERIAL_LIGHT_SHADER_TOON: {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
lcode += "float NdotL = dot( NORMAL, LIGHT_DIR );";
|
|
|
|
lcode += "vec3 light_ref = reflect( LIGHT_DIR, NORMAL );";
|
|
|
|
lcode += "float eye_light = clamp( dot( light_ref, vec3(0,0,0)-EYE_VEC), 0.0, 1.0 );";
|
|
|
|
lcode += "float NdotL_diffuse = smoothstep( max( SHADE_PARAM-0.05, 0.0-1.0), min( SHADE_PARAM+0.05, 1.0), NdotL );";
|
|
|
|
lcode += "float spec_radius=clamp((1.0-(SPECULAR_EXP/64.0)),0.0,1.0);";
|
|
|
|
lcode += "float NdotL_specular = smoothstep( max( spec_radius-0.05, 0.0), min( spec_radius+0.05, 1.0), eye_light )*max(NdotL,0);";
|
|
|
|
lcode += "LIGHT = NdotL_diffuse * LIGHT_DIFFUSE*DIFFUSE + NdotL_specular * LIGHT_SPECULAR*SPECULAR;";
|
2014-06-28 04:21:45 +02:00
|
|
|
|
|
|
|
} break;
|
|
|
|
}
|
|
|
|
|
2014-02-10 02:10:30 +01:00
|
|
|
//print_line("**VERTEX SHADER GENERATED code: \n"+vcode);
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
double tf = (OS::get_singleton()->get_ticks_usec() - t) / 1000.0;
|
|
|
|
// print_line("generate: "+rtos(tf));
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
shader_set_code(fms.shader, vcode, code, lcode, 0, 0);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
fixed_material_shaders[p_key] = fms;
|
2014-02-10 02:10:30 +01:00
|
|
|
return fms.shader;
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
void Rasterizer::_free_shader(const FixedMaterialShaderKey &p_key) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_key.valid == 0)
|
2014-02-10 02:10:30 +01:00
|
|
|
return; //not a valid key
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
Map<FixedMaterialShaderKey, FixedMaterialShader>::Element *E = fixed_material_shaders.find(p_key);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
ERR_FAIL_COND(!E);
|
|
|
|
E->get().refcount--;
|
2017-03-19 00:36:26 +01:00
|
|
|
if (E->get().refcount == 0) {
|
2014-02-10 02:10:30 +01:00
|
|
|
free(E->get().shader);
|
|
|
|
fixed_material_shaders.erase(E);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Rasterizer::fixed_material_set_flag(RID p_material, VS::FixedMaterialFlags p_flag, bool p_enabled) {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
2014-02-10 02:10:30 +01:00
|
|
|
ERR_FAIL_COND(!E);
|
2017-03-19 00:36:26 +01:00
|
|
|
FixedMaterial &fm = *E->get();
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
switch (p_flag) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
case VS::FIXED_MATERIAL_FLAG_USE_ALPHA: fm.use_alpha = p_enabled; break;
|
|
|
|
case VS::FIXED_MATERIAL_FLAG_USE_COLOR_ARRAY: fm.use_color_array = p_enabled; break;
|
|
|
|
case VS::FIXED_MATERIAL_FLAG_USE_POINT_SIZE: fm.use_pointsize = p_enabled; break;
|
|
|
|
case VS::FIXED_MATERIAL_FLAG_DISCARD_ALPHA: fm.discard_alpha = p_enabled; break;
|
|
|
|
case VS::FIXED_MATERIAL_FLAG_USE_XY_NORMALMAP: fm.use_xy_normalmap = p_enabled; break;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!fm.dirty_list.in_list())
|
2017-03-19 00:36:26 +01:00
|
|
|
fixed_material_dirty_list.add(&fm.dirty_list);
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
bool Rasterizer::fixed_material_get_flag(RID p_material, VS::FixedMaterialFlags p_flag) const {
|
|
|
|
|
|
|
|
const Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
|
|
|
ERR_FAIL_COND_V(!E, false);
|
|
|
|
const FixedMaterial &fm = *E->get();
|
|
|
|
switch (p_flag) {
|
|
|
|
|
|
|
|
case VS::FIXED_MATERIAL_FLAG_USE_ALPHA:
|
|
|
|
return fm.use_alpha;
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case VS::FIXED_MATERIAL_FLAG_USE_COLOR_ARRAY:
|
|
|
|
return fm.use_color_array;
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case VS::FIXED_MATERIAL_FLAG_USE_POINT_SIZE:
|
|
|
|
return fm.use_pointsize;
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case VS::FIXED_MATERIAL_FLAG_DISCARD_ALPHA:
|
|
|
|
return fm.discard_alpha;
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case VS::FIXED_MATERIAL_FLAG_USE_XY_NORMALMAP:
|
|
|
|
return fm.use_xy_normalmap;
|
|
|
|
;
|
|
|
|
break;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
RID Rasterizer::fixed_material_create() {
|
|
|
|
|
|
|
|
RID mat = material_create();
|
2017-03-19 00:36:26 +01:00
|
|
|
fixed_materials[mat] = memnew(FixedMaterial());
|
|
|
|
FixedMaterial &fm = *fixed_materials[mat];
|
|
|
|
fm.self = mat;
|
2014-02-10 02:10:30 +01:00
|
|
|
fm.get_key();
|
2017-03-19 00:36:26 +01:00
|
|
|
material_set_flag(mat, VS::MATERIAL_FLAG_COLOR_ARRAY_SRGB, true);
|
|
|
|
for (int i = 0; i < VS::FIXED_MATERIAL_PARAM_MAX; i++) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
material_set_param(mat, _fixed_material_param_names[i], fm.param[i]); //must be there
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
fixed_material_dirty_list.add(&fm.dirty_list);
|
2016-03-09 00:00:52 +01:00
|
|
|
//print_line("FMC: "+itos(mat.get_id()));
|
2014-02-10 02:10:30 +01:00
|
|
|
return mat;
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
void Rasterizer::fixed_material_set_parameter(RID p_material, VS::FixedMaterialParam p_parameter, const Variant &p_value) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
2014-02-10 02:10:30 +01:00
|
|
|
ERR_FAIL_COND(!E);
|
2017-03-19 00:36:26 +01:00
|
|
|
FixedMaterial &fm = *E->get();
|
|
|
|
RID material = E->key();
|
|
|
|
ERR_FAIL_INDEX(p_parameter, VS::FIXED_MATERIAL_PARAM_MAX);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if ((p_parameter == VS::FIXED_MATERIAL_PARAM_DIFFUSE || p_parameter == VS::FIXED_MATERIAL_PARAM_SPECULAR || p_parameter == VS::FIXED_MATERIAL_PARAM_EMISSION)) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (p_value.get_type() != Variant::COLOR) {
|
|
|
|
ERR_EXPLAIN(String(_fixed_material_param_names[p_parameter]) + " expects Color");
|
2014-02-10 02:10:30 +01:00
|
|
|
ERR_FAIL();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (!p_value.is_num()) {
|
2017-03-19 00:36:26 +01:00
|
|
|
ERR_EXPLAIN(String(_fixed_material_param_names[p_parameter]) + " expects scalar");
|
2014-02-10 02:10:30 +01:00
|
|
|
ERR_FAIL();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
fm.param[p_parameter] = p_value;
|
|
|
|
VS::get_singleton()->material_set_param(material, _fixed_material_param_names[p_parameter], p_value);
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
2017-03-19 00:36:26 +01:00
|
|
|
Variant Rasterizer::fixed_material_get_parameter(RID p_material, VS::FixedMaterialParam p_parameter) const {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
const Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
|
|
|
ERR_FAIL_COND_V(!E, Variant());
|
|
|
|
const FixedMaterial &fm = *E->get();
|
|
|
|
ERR_FAIL_INDEX_V(p_parameter, VS::FIXED_MATERIAL_PARAM_MAX, Variant());
|
2014-02-10 02:10:30 +01:00
|
|
|
return fm.param[p_parameter];
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
void Rasterizer::fixed_material_set_texture(RID p_material, VS::FixedMaterialParam p_parameter, RID p_texture) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
2014-02-10 02:10:30 +01:00
|
|
|
if (!E) {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
print_line("Not found: " + itos(p_material.get_id()));
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
ERR_FAIL_COND(!E);
|
2017-03-19 00:36:26 +01:00
|
|
|
FixedMaterial &fm = *E->get();
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
ERR_FAIL_INDEX(p_parameter, VS::FIXED_MATERIAL_PARAM_MAX);
|
|
|
|
RID material = E->key();
|
|
|
|
fm.texture[p_parameter] = p_texture;
|
|
|
|
VS::get_singleton()->material_set_param(material, _fixed_material_tex_names[p_parameter], p_texture);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
if (!fm.dirty_list.in_list())
|
2017-03-19 00:36:26 +01:00
|
|
|
fixed_material_dirty_list.add(&fm.dirty_list);
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
2017-03-19 00:36:26 +01:00
|
|
|
RID Rasterizer::fixed_material_get_texture(RID p_material, VS::FixedMaterialParam p_parameter) const {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
const Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
|
|
|
ERR_FAIL_COND_V(!E, RID());
|
|
|
|
const FixedMaterial &fm = *E->get();
|
|
|
|
ERR_FAIL_INDEX_V(p_parameter, VS::FIXED_MATERIAL_PARAM_MAX, RID());
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
return fm.texture[p_parameter];
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
void Rasterizer::fixed_material_set_texcoord_mode(RID p_material, VS::FixedMaterialParam p_parameter, VS::FixedMaterialTexCoordMode p_mode) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
2014-02-10 02:10:30 +01:00
|
|
|
ERR_FAIL_COND(!E);
|
2017-03-19 00:36:26 +01:00
|
|
|
FixedMaterial &fm = *E->get();
|
|
|
|
ERR_FAIL_INDEX(p_parameter, VS::FIXED_MATERIAL_PARAM_MAX);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
fm.get_key();
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
fm.texture_tc[p_parameter] = p_mode;
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
if (!fm.dirty_list.in_list())
|
2017-03-19 00:36:26 +01:00
|
|
|
fixed_material_dirty_list.add(&fm.dirty_list);
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
VS::FixedMaterialTexCoordMode Rasterizer::fixed_material_get_texcoord_mode(RID p_material, VS::FixedMaterialParam p_parameter) const {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
const Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
|
|
|
ERR_FAIL_COND_V(!E, VS::FIXED_MATERIAL_TEXCOORD_UV);
|
|
|
|
const FixedMaterial &fm = *E->get();
|
|
|
|
ERR_FAIL_INDEX_V(p_parameter, VS::FIXED_MATERIAL_PARAM_MAX, VS::FIXED_MATERIAL_TEXCOORD_UV);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
return fm.texture_tc[p_parameter];
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
void Rasterizer::fixed_material_set_uv_transform(RID p_material, const Transform &p_transform) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
2014-02-10 02:10:30 +01:00
|
|
|
ERR_FAIL_COND(!E);
|
2017-03-19 00:36:26 +01:00
|
|
|
FixedMaterial &fm = *E->get();
|
|
|
|
RID material = E->key();
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
VS::get_singleton()->material_set_param(material, _fixed_material_uv_xform_name, p_transform);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
fm.uv_xform = p_transform;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Transform Rasterizer::fixed_material_get_uv_transform(RID p_material) const {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
const Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
|
|
|
ERR_FAIL_COND_V(!E, Transform());
|
|
|
|
const FixedMaterial &fm = *E->get();
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
return fm.uv_xform;
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
void Rasterizer::fixed_material_set_light_shader(RID p_material, VS::FixedMaterialLightShader p_shader) {
|
2014-06-28 04:21:45 +02:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
2014-06-28 04:21:45 +02:00
|
|
|
ERR_FAIL_COND(!E);
|
2017-03-19 00:36:26 +01:00
|
|
|
FixedMaterial &fm = *E->get();
|
2014-06-28 04:21:45 +02:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
fm.light_shader = p_shader;
|
2014-06-28 04:21:45 +02:00
|
|
|
|
|
|
|
if (!fm.dirty_list.in_list())
|
2017-03-19 00:36:26 +01:00
|
|
|
fixed_material_dirty_list.add(&fm.dirty_list);
|
2014-06-28 04:21:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
VS::FixedMaterialLightShader Rasterizer::fixed_material_get_light_shader(RID p_material) const {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
const Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
|
|
|
ERR_FAIL_COND_V(!E, VS::FIXED_MATERIAL_LIGHT_SHADER_LAMBERT);
|
|
|
|
const FixedMaterial &fm = *E->get();
|
2014-06-28 04:21:45 +02:00
|
|
|
|
|
|
|
return fm.light_shader;
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
void Rasterizer::fixed_material_set_point_size(RID p_material, float p_size) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
2014-02-10 02:10:30 +01:00
|
|
|
ERR_FAIL_COND(!E);
|
2017-03-19 00:36:26 +01:00
|
|
|
FixedMaterial &fm = *E->get();
|
|
|
|
RID material = E->key();
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
VS::get_singleton()->material_set_param(material, _fixed_material_point_size_name, p_size);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
fm.point_size = p_size;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
float Rasterizer::fixed_material_get_point_size(RID p_material) const {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
const Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
|
|
|
ERR_FAIL_COND_V(!E, 1.0);
|
|
|
|
const FixedMaterial &fm = *E->get();
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
return fm.point_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Rasterizer::_update_fixed_materials() {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
while (fixed_material_dirty_list.first()) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
FixedMaterial &fm = *fixed_material_dirty_list.first()->self();
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
FixedMaterialShaderKey new_key = fm.get_key();
|
2017-03-19 00:36:26 +01:00
|
|
|
if (new_key.key != fm.current_key.key) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
_free_shader(fm.current_key);
|
|
|
|
RID new_rid = _create_shader(new_key);
|
2017-03-19 00:36:26 +01:00
|
|
|
fm.current_key = new_key;
|
|
|
|
material_set_shader(fm.self, new_rid);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
if (fm.texture[VS::FIXED_MATERIAL_PARAM_DETAIL].is_valid()) {
|
|
|
|
//send these again just in case.
|
2017-03-19 00:36:26 +01:00
|
|
|
material_set_param(fm.self, _fixed_material_param_names[VS::FIXED_MATERIAL_PARAM_DETAIL], fm.param[VS::FIXED_MATERIAL_PARAM_DETAIL]);
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
if (fm.texture[VS::FIXED_MATERIAL_PARAM_NORMAL].is_valid()) {
|
|
|
|
//send these again just in case.
|
2017-03-19 00:36:26 +01:00
|
|
|
material_set_param(fm.self, _fixed_material_param_names[VS::FIXED_MATERIAL_PARAM_NORMAL], fm.param[VS::FIXED_MATERIAL_PARAM_NORMAL]);
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
material_set_param(fm.self, _fixed_material_uv_xform_name, fm.uv_xform);
|
2015-01-10 21:35:26 +01:00
|
|
|
if (fm.use_pointsize) {
|
2017-03-19 00:36:26 +01:00
|
|
|
material_set_param(fm.self, _fixed_material_point_size_name, fm.point_size);
|
2015-01-10 21:35:26 +01:00
|
|
|
}
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fixed_material_dirty_list.remove(fixed_material_dirty_list.first());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
void Rasterizer::_free_fixed_material(const RID &p_material) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
Map<RID, FixedMaterial *>::Element *E = fixed_materials.find(p_material);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
if (E) {
|
|
|
|
|
|
|
|
_free_shader(E->get()->current_key); //free shader
|
|
|
|
if (E->get()->dirty_list.in_list())
|
2017-03-19 00:36:26 +01:00
|
|
|
fixed_material_dirty_list.remove(&E->get()->dirty_list);
|
2014-02-10 02:10:30 +01:00
|
|
|
memdelete(E->get());
|
|
|
|
fixed_materials.erase(E); //free material
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Rasterizer::flush_frame() {
|
|
|
|
|
|
|
|
//not really necesary to implement
|
|
|
|
}
|
|
|
|
|
|
|
|
Rasterizer::Rasterizer() {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
static const char *fm_names[VS::FIXED_MATERIAL_PARAM_MAX] = {
|
|
|
|
"diffuse",
|
|
|
|
"detail",
|
|
|
|
"specular",
|
|
|
|
"emission",
|
|
|
|
"specular_exp",
|
|
|
|
"glow",
|
|
|
|
"normal",
|
|
|
|
"shade_param"
|
|
|
|
};
|
|
|
|
|
|
|
|
for (int i = 0; i < VS::FIXED_MATERIAL_PARAM_MAX; i++) {
|
|
|
|
|
|
|
|
_fixed_material_param_names[i] = String("fmp_") + fm_names[i];
|
|
|
|
_fixed_material_tex_names[i] = String("fmp_") + fm_names[i] + "_tex";
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
_fixed_material_uv_xform_name = "fmp_uv_xform";
|
|
|
|
_fixed_material_point_size_name = "fmp_point_size";
|
2015-01-10 21:35:26 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
draw_viewport_func = NULL;
|
2014-10-03 13:58:41 +02:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
ERR_FAIL_COND(sizeof(FixedMaterialShaderKey) != 4);
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
RID Rasterizer::create_overdraw_debug_material() {
|
|
|
|
RID mat = fixed_material_create();
|
2017-03-19 00:36:26 +01:00
|
|
|
fixed_material_set_parameter(mat, VisualServer::FIXED_MATERIAL_PARAM_SPECULAR, Color(0, 0, 0));
|
|
|
|
fixed_material_set_parameter(mat, VisualServer::FIXED_MATERIAL_PARAM_DIFFUSE, Color(0.1, 0.1, 0.2));
|
|
|
|
fixed_material_set_parameter(mat, VisualServer::FIXED_MATERIAL_PARAM_EMISSION, Color(0, 0, 0));
|
|
|
|
fixed_material_set_flag(mat, VS::FIXED_MATERIAL_FLAG_USE_ALPHA, true);
|
|
|
|
material_set_flag(mat, VisualServer::MATERIAL_FLAG_UNSHADED, true);
|
|
|
|
material_set_blend_mode(mat, VisualServer::MATERIAL_BLEND_MODE_ADD);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
return mat;
|
|
|
|
}
|