From 022d12a721b04799423edf646b2a08bd48edfe6a Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 7 May 2012 10:53:09 +0000 Subject: [PATCH] Fix most of #31307: cycles panorama camera not working correct with speed vectors and window texture coordinates. Only for Fisheye Equisolid it's still not working correct yet. Patch from Dalai with modifications. --- intern/cycles/kernel/kernel_camera.h | 13 -- intern/cycles/kernel/kernel_montecarlo.h | 118 ------------- intern/cycles/kernel/kernel_path.h | 1 + intern/cycles/kernel/kernel_projection.h | 207 +++++++++++++++++++++++ intern/cycles/kernel/kernel_triangle.h | 41 ++++- intern/cycles/kernel/kernel_types.h | 4 +- intern/cycles/kernel/svm/svm_tex_coord.h | 46 ++--- intern/cycles/render/camera.cpp | 26 ++- intern/cycles/render/light.cpp | 2 - intern/cycles/util/util_math.h | 5 + intern/cycles/util/util_transform.h | 4 +- 11 files changed, 300 insertions(+), 167 deletions(-) create mode 100644 intern/cycles/kernel/kernel_projection.h diff --git a/intern/cycles/kernel/kernel_camera.h b/intern/cycles/kernel/kernel_camera.h index 220ebec13ce..e1b474d8537 100644 --- a/intern/cycles/kernel/kernel_camera.h +++ b/intern/cycles/kernel/kernel_camera.h @@ -134,19 +134,6 @@ __device void camera_sample_orthographic(KernelGlobals *kg, float raster_x, floa /* Panorama Camera */ -__device float3 panorama_to_direction(KernelGlobals *kg, float u, float v) -{ - switch(kernel_data.cam.panorama_type) { - case PANORAMA_EQUIRECTANGULAR: - return equirectangular_to_direction(u, v); - case PANORAMA_FISHEYE_EQUIDISTANT: - return fisheye_to_direction(u, v, kernel_data.cam.fisheye_fov); - case PANORAMA_FISHEYE_EQUISOLID: - default: - return fisheye_equisolid_to_direction(u, v, kernel_data.cam.fisheye_lens, kernel_data.cam.fisheye_fov, kernel_data.cam.sensorwidth, kernel_data.cam.sensorheight); - } -} - __device void camera_sample_panorama(KernelGlobals *kg, float raster_x, float raster_y, Ray *ray) { Transform rastertocamera = kernel_data.cam.rastertocamera; diff --git a/intern/cycles/kernel/kernel_montecarlo.h b/intern/cycles/kernel/kernel_montecarlo.h index f51ce263ea2..bdd147f83d3 100644 --- a/intern/cycles/kernel/kernel_montecarlo.h +++ b/intern/cycles/kernel/kernel_montecarlo.h @@ -185,124 +185,6 @@ __device float2 regular_polygon_sample(float corners, float rotation, float u, f return make_float2(cr*p.x - sr*p.y, sr*p.x + cr*p.y); } -/* Spherical coordinates <-> Cartesian direction */ - -__device float2 direction_to_spherical(float3 dir) -{ - float theta = acosf(dir.z); - float phi = atan2f(dir.x, dir.y); - - return make_float2(theta, phi); -} - -__device float3 spherical_to_direction(float theta, float phi) -{ - return make_float3( - sinf(theta)*cosf(phi), - sinf(theta)*sinf(phi), - cosf(theta)); -} - -/* Equirectangular coordinates <-> Cartesian direction */ - -__device float2 direction_to_equirectangular(float3 dir) -{ - float u = -atan2f(dir.y, dir.x)/(2.0f*M_PI_F) + 0.5f; - float v = atan2f(dir.z, hypotf(dir.x, dir.y))/M_PI_F + 0.5f; - - return make_float2(u, v); -} - -__device float3 equirectangular_to_direction(float u, float v) -{ - float phi = M_PI_F*(1.0f - 2.0f*u); - float theta = M_PI_F*(1.0f - v); - - return make_float3( - sin(theta)*cos(phi), - sin(theta)*sin(phi), - cos(theta)); -} - -/* Fisheye <- Cartesian direction */ - -__device float3 fisheye_to_direction(float u, float v, float fov) -{ - u = (u - 0.5f) * 2.0f; - v = (v - 0.5f) * 2.0f; - - float r = sqrt(u*u + v*v); - - if(r > 1.0f) - return make_float3(0.0f, 0.0f, 0.0f); - - float phi = acosf((r != 0.0f)? u/r: 0.0f); - float theta = asinf(r) * (fov / M_PI_F); - - if(v < 0.0f) phi = -phi; - - return make_float3( - cosf(theta), - -cosf(phi)*sinf(theta), - sinf(phi)*sinf(theta) - ); -} - -__device float3 fisheye_equisolid_to_direction(float u, float v, float lens, float fov, float width, float height) -{ - u = (u - 0.5f) * width; - v = (v - 0.5f) * height; - - float rmax = 2.0f * lens * sinf(fov * 0.25f); - float r = sqrt(u*u + v*v); - - if(r > rmax) - return make_float3(0.0f, 0.0f, 0.0f); - - float phi = acosf((r != 0.0f)? u/r: 0.0f); - float theta = 2.0f * asinf(r/(2.0f * lens)); - - if(v < 0.0f) phi = -phi; - - return make_float3( - cosf(theta), - -cosf(phi)*sinf(theta), - sinf(phi)*sinf(theta) - ); -} - -/* Mirror Ball <-> Cartesion direction */ - -__device float3 mirrorball_to_direction(float u, float v) -{ - /* point on sphere */ - float3 dir; - - dir.x = 2.0f*u - 1.0f; - dir.z = 2.0f*v - 1.0f; - dir.y = -sqrt(max(1.0f - dir.x*dir.x - dir.z*dir.z, 0.0f)); - - /* reflection */ - float3 I = make_float3(0.0f, -1.0f, 0.0f); - - return 2.0f*dot(dir, I)*dir - I; -} - -__device float2 direction_to_mirrorball(float3 dir) -{ - /* inverse of mirrorball_to_direction */ - dir.y -= 1.0f; - - float div = 2.0f*sqrt(max(-0.5f*dir.y, 0.0f)); - if(div > 0.0f) - dir /= div; - - float u = 0.5f*(dir.x + 1.0f); - float v = 0.5f*(dir.z + 1.0f); - - return make_float2(u, v); -} - CCL_NAMESPACE_END #endif /* __KERNEL_MONTECARLO_CL__ */ diff --git a/intern/cycles/kernel/kernel_path.h b/intern/cycles/kernel/kernel_path.h index d53951a1f34..e0e17ee57dc 100644 --- a/intern/cycles/kernel/kernel_path.h +++ b/intern/cycles/kernel/kernel_path.h @@ -18,6 +18,7 @@ #include "kernel_differential.h" #include "kernel_montecarlo.h" +#include "kernel_projection.h" #include "kernel_object.h" #include "kernel_triangle.h" #ifdef __QBVH__ diff --git a/intern/cycles/kernel/kernel_projection.h b/intern/cycles/kernel/kernel_projection.h new file mode 100644 index 00000000000..43535182d61 --- /dev/null +++ b/intern/cycles/kernel/kernel_projection.h @@ -0,0 +1,207 @@ +/* + * Parts adapted from Open Shading Language with this license: + * + * Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al. + * All Rights Reserved. + * + * Modifications Copyright 2011, Blender Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sony Pictures Imageworks nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __KERNEL_PROJECTION_CL__ +#define __KERNEL_PROJECTION_CL__ + +CCL_NAMESPACE_BEGIN + +/* Spherical coordinates <-> Cartesian direction */ + +__device float2 direction_to_spherical(float3 dir) +{ + float theta = acosf(dir.z); + float phi = atan2f(dir.x, dir.y); + + return make_float2(theta, phi); +} + +__device float3 spherical_to_direction(float theta, float phi) +{ + return make_float3( + sinf(theta)*cosf(phi), + sinf(theta)*sinf(phi), + cosf(theta)); +} + +/* Equirectangular coordinates <-> Cartesian direction */ + +__device float2 direction_to_equirectangular(float3 dir) +{ + float u = -atan2f(dir.y, dir.x)/(2.0f*M_PI_F) + 0.5f; + float v = atan2f(dir.z, hypotf(dir.x, dir.y))/M_PI_F + 0.5f; + + return make_float2(u, v); +} + +__device float3 equirectangular_to_direction(float u, float v) +{ + float phi = M_PI_F*(1.0f - 2.0f*u); + float theta = M_PI_F*(1.0f - v); + + return make_float3( + sin(theta)*cos(phi), + sin(theta)*sin(phi), + cos(theta)); +} + +/* Fisheye <- Cartesian direction */ + +__device float2 direction_to_fisheye(float3 dir, float fov) +{ + float r = atan2f(sqrt(dir.y*dir.y + dir.z*dir.z), dir.x) / fov; + float phi = atan2(dir.z, dir.y); + + float u = r * cos(phi) + 0.5f; + float v = r * sin(phi) + 0.5f; + + return make_float2(u, v); +} + +__device float3 fisheye_to_direction(float u, float v, float fov) +{ + u = (u - 0.5f) * 2.0f; + v = (v - 0.5f) * 2.0f; + + float r = sqrt(u*u + v*v); + + if(r > 1.0f) + return make_float3(0.0f, 0.0f, 0.0f); + + float phi = acosf((r != 0.0f)? u/r: 0.0f); + float theta = asinf(r) * (fov / M_PI_F); + + if(v < 0.0f) phi = -phi; + + return make_float3( + cosf(theta), + -cosf(phi)*sinf(theta), + sinf(phi)*sinf(theta) + ); +} + +__device float2 direction_to_fisheye_equisolid(float3 dir, float lens, float fov, float width, float height) +{ + /* XXX not implemented yet */ + float u = -atan2f(dir.y, dir.x)/(2.0f*M_PI_F) + 0.5f; + float v = atan2f(dir.z, hypotf(dir.x, dir.y))/M_PI_F + 0.5f; + + return make_float2(u, v); +} + +__device float3 fisheye_equisolid_to_direction(float u, float v, float lens, float fov, float width, float height) +{ + u = (u - 0.5f) * width; + v = (v - 0.5f) * height; + + float rmax = 2.0f * lens * sinf(fov * 0.25f); + float r = sqrt(u*u + v*v); + + if(r > rmax) + return make_float3(0.0f, 0.0f, 0.0f); + + float phi = acosf((r != 0.0f)? u/r: 0.0f); + float theta = 2.0f * asinf(r/(2.0f * lens)); + + if(v < 0.0f) phi = -phi; + + return make_float3( + cosf(theta), + -cosf(phi)*sinf(theta), + sinf(phi)*sinf(theta) + ); +} + +/* Mirror Ball <-> Cartesion direction */ + +__device float3 mirrorball_to_direction(float u, float v) +{ + /* point on sphere */ + float3 dir; + + dir.x = 2.0f*u - 1.0f; + dir.z = 2.0f*v - 1.0f; + dir.y = -sqrt(max(1.0f - dir.x*dir.x - dir.z*dir.z, 0.0f)); + + /* reflection */ + float3 I = make_float3(0.0f, -1.0f, 0.0f); + + return 2.0f*dot(dir, I)*dir - I; +} + +__device float2 direction_to_mirrorball(float3 dir) +{ + /* inverse of mirrorball_to_direction */ + dir.y -= 1.0f; + + float div = 2.0f*sqrt(max(-0.5f*dir.y, 0.0f)); + if(div > 0.0f) + dir /= div; + + float u = 0.5f*(dir.x + 1.0f); + float v = 0.5f*(dir.z + 1.0f); + + return make_float2(u, v); +} + +__device float3 panorama_to_direction(KernelGlobals *kg, float u, float v) +{ + switch(kernel_data.cam.panorama_type) { + case PANORAMA_EQUIRECTANGULAR: + return equirectangular_to_direction(u, v); + case PANORAMA_FISHEYE_EQUIDISTANT: + return fisheye_to_direction(u, v, kernel_data.cam.fisheye_fov); + case PANORAMA_FISHEYE_EQUISOLID: + default: + return fisheye_equisolid_to_direction(u, v, kernel_data.cam.fisheye_lens, + kernel_data.cam.fisheye_fov, kernel_data.cam.sensorwidth, kernel_data.cam.sensorheight); + } +} + +__device float2 direction_to_panorama(KernelGlobals *kg, float3 dir) +{ + switch(kernel_data.cam.panorama_type) { + case PANORAMA_EQUIRECTANGULAR: + return direction_to_equirectangular(dir); + case PANORAMA_FISHEYE_EQUIDISTANT: + return direction_to_fisheye(dir, kernel_data.cam.fisheye_fov); + case PANORAMA_FISHEYE_EQUISOLID: + default: + return direction_to_fisheye_equisolid(dir, kernel_data.cam.fisheye_lens, + kernel_data.cam.fisheye_fov, kernel_data.cam.sensorwidth, kernel_data.cam.sensorheight); + } +} + +CCL_NAMESPACE_END + +#endif /* __KERNEL_PROJECTION_CL__ */ + diff --git a/intern/cycles/kernel/kernel_triangle.h b/intern/cycles/kernel/kernel_triangle.h index 1b3956c1dd4..674c3b52539 100644 --- a/intern/cycles/kernel/kernel_triangle.h +++ b/intern/cycles/kernel/kernel_triangle.h @@ -217,15 +217,42 @@ __device float4 triangle_motion_vector(KernelGlobals *kg, ShaderData *sd) tfm = object_fetch_transform(kg, sd->object, TIME_INVALID, OBJECT_TRANSFORM_MOTION_POST); motion_post = transform_point(&tfm, motion_post); - /* camera motion */ - tfm = kernel_data.cam.worldtoraster; - float3 P = transform_perspective(&tfm, sd->P); + float3 P; - tfm = kernel_data.cam.motion.pre; - motion_pre = transform_perspective(&tfm, motion_pre) - P; + /* camera motion, for perspective/orthographic motion.pre/post will be a + world-to-raster matrix, for panorama it's world-to-camera */ + if (kernel_data.cam.type != CAMERA_PANORAMA) { + tfm = kernel_data.cam.worldtoraster; + P = transform_perspective(&tfm, sd->P); - tfm = kernel_data.cam.motion.post; - motion_post = P - transform_perspective(&tfm, motion_post); + tfm = kernel_data.cam.motion.pre; + motion_pre = transform_perspective(&tfm, motion_pre); + + tfm = kernel_data.cam.motion.post; + motion_post = transform_perspective(&tfm, motion_post); + } + else { + tfm = kernel_data.cam.worldtocamera; + P = normalize(transform_point(&tfm, sd->P)); + P = float2_to_float3(direction_to_panorama(kg, P)); + P.x *= kernel_data.cam.width; + P.y *= kernel_data.cam.height; + + tfm = kernel_data.cam.motion.pre; + motion_pre = normalize(transform_point(&tfm, motion_pre)); + motion_pre = float2_to_float3(direction_to_panorama(kg, motion_pre)); + motion_pre.x *= kernel_data.cam.width; + motion_pre.y *= kernel_data.cam.height; + + tfm = kernel_data.cam.motion.post; + motion_post = normalize(transform_point(&tfm, motion_post)); + motion_post = float2_to_float3(direction_to_panorama(kg, motion_post)); + motion_post.x *= kernel_data.cam.width; + motion_post.y *= kernel_data.cam.height; + } + + motion_pre = motion_pre - P; + motion_post = P - motion_post; return make_float4(motion_pre.x, motion_pre.y, motion_post.x, motion_post.y); } diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h index 8c4334d61f3..edca9f8d34d 100644 --- a/intern/cycles/kernel/kernel_types.h +++ b/intern/cycles/kernel/kernel_types.h @@ -491,7 +491,9 @@ typedef struct KernelCamera { /* sensor size */ float sensorwidth; float sensorheight; - int pad1, pad2; + + /* render size */ + float width, height; /* more matrices */ Transform screentoworld; diff --git a/intern/cycles/kernel/svm/svm_tex_coord.h b/intern/cycles/kernel/svm/svm_tex_coord.h index 3b73cac5430..169307574a2 100644 --- a/intern/cycles/kernel/svm/svm_tex_coord.h +++ b/intern/cycles/kernel/svm/svm_tex_coord.h @@ -20,12 +20,35 @@ CCL_NAMESPACE_BEGIN /* Texture Coordinate Node */ -__device float3 svm_background_offset(KernelGlobals *kg) +__device_inline float3 svm_background_offset(KernelGlobals *kg) { Transform cameratoworld = kernel_data.cam.cameratoworld; return make_float3(cameratoworld.x.w, cameratoworld.y.w, cameratoworld.z.w); } +__device_inline float3 svm_world_to_ndc(KernelGlobals *kg, ShaderData *sd, float3 P) +{ + if(kernel_data.cam.type != CAMERA_PANORAMA) { + if(sd->object != ~0) + P += svm_background_offset(kg); + + Transform tfm = kernel_data.cam.worldtondc; + return transform_perspective(&tfm, P); + } + else { + Transform tfm = kernel_data.cam.worldtocamera; + + if(sd->object != ~0) + P = normalize(transform_point(&tfm, P)); + else + P = normalize(transform_direction(&tfm, P)); + + float2 uv = direction_to_panorama(kg, P);; + + return make_float3(uv.x, uv.y, 0.0f); + } +} + __device void svm_node_tex_coord(KernelGlobals *kg, ShaderData *sd, float *stack, uint type, uint out_offset) { float3 data; @@ -59,12 +82,7 @@ __device void svm_node_tex_coord(KernelGlobals *kg, ShaderData *sd, float *stack break; } case NODE_TEXCO_WINDOW: { - Transform tfm = kernel_data.cam.worldtondc; - - if(sd->object != ~0) - data = transform_perspective(&tfm, sd->P); - else - data = transform_perspective(&tfm, sd->P + svm_background_offset(kg)); + data = svm_world_to_ndc(kg, sd, sd->P); break; } case NODE_TEXCO_REFLECTION: { @@ -113,12 +131,7 @@ __device void svm_node_tex_coord_bump_dx(KernelGlobals *kg, ShaderData *sd, floa break; } case NODE_TEXCO_WINDOW: { - Transform tfm = kernel_data.cam.worldtondc; - - if(sd->object != ~0) - data = transform_perspective(&tfm, sd->P + sd->dP.dx); - else - data = transform_perspective(&tfm, sd->P + sd->dP.dx + svm_background_offset(kg)); + data = svm_world_to_ndc(kg, sd, sd->P + sd->dP.dx); break; } case NODE_TEXCO_REFLECTION: { @@ -170,12 +183,7 @@ __device void svm_node_tex_coord_bump_dy(KernelGlobals *kg, ShaderData *sd, floa break; } case NODE_TEXCO_WINDOW: { - Transform tfm = kernel_data.cam.worldtondc; - - if(sd->object != ~0) - data = transform_perspective(&tfm, sd->P + sd->dP.dy); - else - data = transform_perspective(&tfm, sd->P + sd->dP.dy + svm_background_offset(kg)); + data = svm_world_to_ndc(kg, sd, sd->P + sd->dP.dy); break; } case NODE_TEXCO_REFLECTION: { diff --git a/intern/cycles/render/camera.cpp b/intern/cycles/render/camera.cpp index 95405519cc0..3ecffab7cbc 100644 --- a/intern/cycles/render/camera.cpp +++ b/intern/cycles/render/camera.cpp @@ -158,13 +158,25 @@ void Camera::device_update(Device *device, DeviceScene *dscene, Scene *scene) kcam->have_motion = 0; if(need_motion == Scene::MOTION_PASS) { - if(use_motion) { - kcam->motion.pre = transform_inverse(motion.pre * rastertocamera); - kcam->motion.post = transform_inverse(motion.post * rastertocamera); + if(type == CAMERA_PANORAMA) { + if(use_motion) { + kcam->motion.pre = transform_inverse(motion.pre); + kcam->motion.post = transform_inverse(motion.post); + } + else { + kcam->motion.pre = kcam->worldtocamera; + kcam->motion.post = kcam->worldtocamera; + } } else { - kcam->motion.pre = worldtoraster; - kcam->motion.post = worldtoraster; + if(use_motion) { + kcam->motion.pre = transform_inverse(motion.pre * rastertocamera); + kcam->motion.post = transform_inverse(motion.post * rastertocamera); + } + else { + kcam->motion.pre = worldtoraster; + kcam->motion.post = worldtoraster; + } } } else if(need_motion == Scene::MOTION_BLUR) { @@ -196,6 +208,10 @@ void Camera::device_update(Device *device, DeviceScene *dscene, Scene *scene) kcam->sensorwidth = sensorwidth; kcam->sensorheight = sensorheight; + /* render size */ + kcam->width = width; + kcam->height = height; + /* store differentials */ kcam->dx = float3_to_float4(dx); kcam->dy = float3_to_float4(dy); diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp index 316e5cec9aa..267cb8e6d3a 100644 --- a/intern/cycles/render/light.cpp +++ b/intern/cycles/render/light.cpp @@ -26,8 +26,6 @@ #include "util_foreach.h" #include "util_progress.h" -#include "kernel_montecarlo.h" - CCL_NAMESPACE_BEGIN static void dump_background_pixels(Device *device, DeviceScene *dscene, int res, vector& pixels) diff --git a/intern/cycles/util/util_math.h b/intern/cycles/util/util_math.h index 7b527241847..8c0e7105b22 100644 --- a/intern/cycles/util/util_math.h +++ b/intern/cycles/util/util_math.h @@ -507,6 +507,11 @@ __device_inline float3 fabs(float3 a) #endif +__device_inline float3 float2_to_float3(const float2 a) +{ + return make_float3(a.x, a.y, 0.0f); +} + __device_inline float3 float4_to_float3(const float4 a) { return make_float3(a.x, a.y, a.z); diff --git a/intern/cycles/util/util_transform.h b/intern/cycles/util/util_transform.h index 7136c185d04..e4897ee6787 100644 --- a/intern/cycles/util/util_transform.h +++ b/intern/cycles/util/util_transform.h @@ -253,7 +253,7 @@ __device_inline bool transform_uniform_scale(const Transform& tfm, float& scale) /* the epsilon here is quite arbitrary, but this function is only used for surface area and bump, where we except it to not be so sensitive */ Transform ttfm = transform_transpose(tfm); - float eps = 1e-7f; + float eps = 1e-6f; float sx = len_squared(float4_to_float3(tfm.x)); float sy = len_squared(float4_to_float3(tfm.y)); @@ -261,7 +261,7 @@ __device_inline bool transform_uniform_scale(const Transform& tfm, float& scale) float stx = len_squared(float4_to_float3(ttfm.x)); float sty = len_squared(float4_to_float3(ttfm.y)); float stz = len_squared(float4_to_float3(ttfm.z)); - + if(fabsf(sx - sy) < eps && fabsf(sx - sz) < eps && fabsf(sx - stx) < eps && fabsf(sx - sty) < eps && fabsf(sx - stz) < eps) {