# Switch all normals to "outside"

• On 08/03/2013 at 06:17, xxxxxxxx wrote:

Hi all,

I have a little problem.

I've a big amount of objects.
Each object is modified with the following instructions:
Select all points and optimize
Select all polygons, untriangulate and generate N-Gons.
The result are many objects. But not always the normals point to outside.

So: I'm looking for an Idea how to select these objects where the normals point to inside of the object.

Does anybody have an idea how to solve the problem??

Thanks a lot

• On 08/03/2013 at 06:48, xxxxxxxx wrote:

it depends. you have only quite limited access to the polygon normals in python.
in pseudocoode:

``````if ((obj_world_coords + point[n] + normal_point[n]).GetLength() >
(obj_world_coords + point[n] ).GetLength()) :
#positive
else :
#negative
``````

you can get the normals with PolygonObject.CreatePhongNormals or you can calculate
the normals for yourself for a polygon, but when the normals are being flipped due to
some modeling operations the results returned won't be the same as in the editor, so
a phong tag is the best way, if you can create one or there is already one.

``````for ply in polygonObject.GetAllPolygons() :
a, b, c = polygonObject.GetPoint(ply.a), polygonObject.GetPoint(ply.b),  polygonObject.GetPoint(ply.c)
normal = ((a - b) % (b - c)).GetNormalized()
``````

• On 10/03/2013 at 03:09, xxxxxxxx wrote:

Hi Ronald,

consistent with non-overlapping (meaning no self-intersections!) and closed meshes.

The c4dtools package now includes this algorithm since version 1.2.5 in the
c4dtools.misc.normalalign module. The source-code can be found here.

The idea is to shoot rays from three different positions in the world into the polygon-object
for each polygon and test the angle between the polygon-normal and the ray. When the
face is "on the opposite side" of the object, therefore the polygon's normal is intended to
backface the camera, this is figured out by how many intersections have occure before the ray
hit the polygon that is currently being tested.

The ray's must be shot from three different positions since otherwise we would not be able to
tell if a polygon's normal points into the right direction when it's surface normal is exactly orthogonal to the ray shot in.

Here's an excerpt from the code:

``````def test_object_normals(op, info=None, logger=None) :
r"""
Tests the polygon-object *op*'s normals if they're pointing to the
in or outside of the object. Returns a list of boolean variables
where each index defines wether the associated polygon's normal
is pointing into the right direction or not.

The algorithm works best on completely closed shapes with one
segment only. The polygon-object should also be valid, therefore not
more than two polygons per edge, etc. The results might be incorrect
with an invalid mesh structure.

:param op: A :class:`c4d.PolygonObject` instance to test.
:param info: A :class:`~c4dtools.utils.PolygonObjectInfo` instance for
the passed object, or None to generate on demand.
:return:   :class:`list` of `bool` and the PolygonObjectInfo
instance.
"""

if not info:
info = PolygonObjectInfo()
info.init(op)
if info.polycount <= 0:
return []

collider = GeRayCollider()
if not collider.Init(op) :
raise RuntimeError('GeRayCollider could not be initialized.')

mg = op.GetMg()
mp = op.GetMp()

# Define three camera position for the object. We could simply use
# one if there wouldn't be the special case where a polygon's normal
# is exactly in an angle of 90°, where we can not define wether the
# normal is correctly aligned or not.

maxp = mp + size + c4d.Vector(size.GetLength() * 2)
cam1 = c4d.Vector(maxp.x, 0, 0)
cam2 = c4d.Vector(0, maxp.y, 0)
cam3 = c4d.Vector(0, 0, maxp.z)

# Check each polygon from each camera position for the angle between
# them. If one of the angles is greater than 90°, the face is pointing
# into the wrong direction.

result = []
iterator = enumerate(zip(info.normals, info.midpoints))
for index, (normal, midpoint) in iterator:
normal_aligned = False

for cam in [cam1, cam2, cam3]:

# Compute the direction vector from the cam to the midpoint
# of the polygon and the ray length to garuantee a hit with
# the polygon.
direction = (midpoint - cam)
length = direction.GetLengthSquared()
direction.Normalize()

# Compute the intersections point from the cam to the midpoint
# of the polygon.
collider.Intersect(cam, direction, length)
intersections = {}
for i in xrange(collider.GetIntersectionCount()) :
isect = collider.GetIntersection(i)

# The GeRayCollider class may yield doubled intersections,
# we filter them out this way.
if isect['face_id'] not in intersections:
intersections[isect['face_id']] = isect

# Sort the intersections by distance to the cam.
intersections = sorted(
intersections.values(),
key=lambda x: x['distance'])

# Find the intersection with the current polygon and how
# many polygons have been intersected before this polygon
# was intersection.
isect_index = -1
isect = None
for i, isect in enumerate(intersections) :
if isect['face_id'] == index:
isect_index = i
break

# We actually *have* to find an intersection, it would be
# a strange error if we wouldn't have found one.
if isect_index < 0:
if logger:
message = "No intersection with face %d from cam %s"
logger.warning(message % (index, cam))
continue

angle = VectorAngle(normal, direction * -1)

# If there has been one intersection with another face before
# the intersection with the current polygon, the polygon is
# assumed to be intended to face away from the camera. Same for
# all other odd numbers of intersection that have occured
# before the intersection with the current face.
if isect_index % 2:
angle = (math.pi / 2) - angle

if not xor(isect['backface'], isect_index % 2) :
normal_aligned = True

result.append(normal_aligned)

return result, info
``````

Note : Requires utf-8 encoding when used, since the comments contain the non-ascii character  °  .

-Niklas
{"ft_lang_label":"__label__en","ft_lang_prob":0.83294535,"math_prob":0.95601594,"size":5978,"snap":"2020-45-2020-50","text_gpt3_token_len":1432,"char_repetition_ratio":0.15266153,"word_repetition_ratio":0.013978494,"special_character_ratio":0.24590164,"punctuation_ratio":0.15102771,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.98762697,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2020-12-03T08:43:51Z\",\"WARC-Record-ID\":\"<urn:uuid:afa6f7bc-1ad6-4636-8d74-6bf04ab8212a>\",\"Content-Length\":\"52787\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:a39f8fbb-686a-4981-a053-ed32bbbf31d1>\",\"WARC-Concurrent-To\":\"<urn:uuid:2c757077-0ef4-440f-b744-5687a5784ce2>\",\"WARC-IP-Address\":\"88.99.93.218\",\"WARC-Target-URI\":\"https://plugincafe.maxon.net/topic/7007/7905_switch-all-normals-to-outside/2\",\"WARC-Payload-Digest\":\"sha1:LADNPZGZ3P35OQEC6LF2JDVGJIQ7JG34\",\"WARC-Block-Digest\":\"sha1:5UF26BIRHU255L6NMVYOEJJUDMMOSMRA\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2020/CC-MAIN-2020-50/CC-MAIN-2020-50_segments_1606141723602.80_warc_CC-MAIN-20201203062440-20201203092440-00232.warc.gz\"}"}