def advanceToChunk(ray, dimension, maxDistance): point, vector = ray inBounds = point in dimension.bounds for pos, face in _cast(point, vector, maxDistance, 16): x, y, z = pos x >>= 4 y >>= 4 z >>= 4 if inBounds and pos not in dimension.bounds: raise RayBoundsError("Ray exited dimension bounds.") inBounds = pos in dimension.bounds if dimension.containsChunk(x, z): chunk = dimension.getChunk(x, z) section = chunk.getSection(y) if section is not None: # if (section.Blocks == 0).all(): # log.warn("Empty section found!!") # continue box = SectionBox(x, y, z) if point in box: return point ixs = rayIntersectsBox(box, ray) if ixs: hitPoint = ixs[0][0] return hitPoint raise RayBoundsError("Ray exited dimension bounds.")
def rayCast(ray, dimension, maxDistance=100): """ Borrowed from https://gamedev.stackexchange.com/questions/47362/cast-ray-to-select-block-in-voxel-game Updates a factor t along each axis to compute the distance from the vector origin (in units of the vector magnitude) to the next integer value (i.e. block edge) along that axis. Return the block position and face of the block touched. Raises MaxDistanceError if the ray exceeded the max distance without hitting any blocks, or if the ray exits or doesn't enter the dimension's bounds. Bypasses non-air blocks until the first air block is found, and only returns when a non-air block is found after the first air block. :param ray: :type ray: Ray :param maxDistance: :type maxDistance: int :param dimension: :type dimension: mceditlib.worldeditor.WorldEditorDimension :return: (point, face) :rtype: """ point, vector = ray if all(v == 0 for v in vector): raise ValueError("Cannot cast with zero direction ray.") bounds = dimension.bounds if point not in bounds: intersects = rayIntersectsBox(bounds, ray) if not intersects: raise RayBoundsError("Ray does not enter dimension bounds.") point = intersects[0][0] point = advanceToChunk(Ray(point, vector), dimension, maxDistance * 4) currentCX, currentCY, currentCZ = point.intfloor() currentChunk = None foundAir = False for pos, face in _cast(point, vector, maxDistance, 1): cx = pos[0] >> 4 cz = pos[2] >> 4 if cx != currentCX or cz != currentCZ: currentCX = cx currentCZ = cz if dimension.containsChunk(cx, cz): currentChunk = dimension.getChunk(cx, cz) # xxxx WorldEditor.recentlyLoadedChunks ID = dimension.getBlockID(*pos) if ID == 0: foundAir = True if ID and foundAir: return Vector(*pos), faces.Face.fromVector(face) raise MaxDistanceError("Ray exceeded max distance.")
def rayCastInBounds(ray, dimension, maxDistance=100, hitAir=False): try: position, face = rayCast(ray, dimension, maxDistance, hitAir) except RayBoundsError: ixs = rayIntersectsBox(dimension.bounds, ray) if ixs: position, face = ixs[0] position = position.intfloor() else: position, face = None, None return position, face
def rayCastInBounds(ray, dimension, maxDistance=100): try: position, face = rayCast(ray, dimension, maxDistance) except RayBoundsError: ixs = rayIntersectsBox(dimension.bounds, ray) if ixs: position, face = ixs[0] position = position.intfloor() else: position, face = None, None return position, face
def rayCast(ray, dimension, maxDistance=100, hitAir=False): """ Borrowed from https://gamedev.stackexchange.com/questions/47362/cast-ray-to-select-block-in-voxel-game Updates a factor t along each axis to compute the distance from the vector origin (in units of the vector magnitude) to the next integer value (i.e. block edge) along that axis. Return the block position and face of the block touched. Raises MaxDistanceError if the ray exceeded the max distance without hitting any blocks, or if the ray exits or doesn't enter the dimension's bounds. :param ray: :type ray: Ray :param maxDistance: :type maxDistance: int :param dimension: :type dimension: mceditlib.worldeditor.WorldEditorDimension :return: (point, face) :rtype: """ point, vector = ray if all(v == 0 for v in vector): raise ValueError("Cannot cast with zero direction ray.") bounds = dimension.bounds if point not in bounds: intersects = rayIntersectsBox(bounds, ray) if not intersects: raise RayBoundsError("Ray does not enter dimension bounds.") point = intersects[0][0] point = advanceToChunk(Ray(point, vector), dimension, maxDistance * 4) currentCX, currentCY, currentCZ = point.intfloor() currentChunk = None for pos, face in _cast(point, vector, maxDistance, 1): cx = pos[0] >> 4 cz = pos[2] >> 4 if cx != currentCX or cz != currentCZ: currentCX = cx currentCZ = cz if dimension.containsChunk(cx, cz): currentChunk = dimension.getChunk( cx, cz) # xxxx WorldEditor.recentlyLoadedChunks ID = dimension.getBlockID(*pos) if ID or hitAir: return Vector(*pos), faces.Face.fromVector(face) raise MaxDistanceError("Ray exceeded max distance.")
def rayCast(ray, dimension, maxDistance=100): """ Borrowed from https://gamedev.stackexchange.com/questions/47362/cast-ray-to-select-block-in-voxel-game Updates a factor t along each axis to compute the distance from the vector origin (in units of the vector magnitude) to the next integer value (i.e. block edge) along that axis. Return the block position and face of the block touched. Raises MaxDistanceError if the ray exceeded the max distance without hitting any blocks, or if the ray exits or doesn't enter the dimension's bounds. Bypasses non-air blocks until the first air block is found, and only returns when a non-air block is found after the first air block. :param ray: :type ray: Ray :param maxDistance: :type maxDistance: int :param dimension: :type dimension: mceditlib.worldeditor.WorldEditorDimension :return: (point, face) :rtype: """ point, vector = ray if all(v == 0 for v in vector): raise ValueError("Cannot cast with zero direction ray.") bounds = dimension.bounds if point not in bounds: intersects = rayIntersectsBox(bounds, ray) if not intersects: raise RayBoundsError("Ray does not enter dimension bounds.") point = intersects[0][0] point = advanceToChunk(Ray(point, vector), dimension, maxDistance * 4) foundAir = False for pos, face in _cast(point, vector, maxDistance, 1): ID = dimension.getBlockID(*pos) if ID == 0: # xxx configurable air blocks? foundAir = True if ID and foundAir: return Vector(*pos), faces.Face.fromVector(face) if pos not in bounds: raise RayBoundsError("Ray exited dimension bounds") raise MaxDistanceError("Ray exceeded max distance.")
def boxFaceUnderCursor(box, mouseRay): """ Find the nearest face of the given bounding box that intersects with the given mouse ray and return (point, face) """ nearPoint, mouseVector = mouseRay intersections = rayIntersectsBox(box, mouseRay) if intersections is None: return None, None point, face = intersections[0] # if the point is near the edge of the face, and the edge is facing away, # return the away-facing face dim = face.dimension dim1 = (dim + 1) % 3 dim2 = (dim + 2) % 3 # determine if a click was within self.edge_factor of the edge of a selection box side. if so, click through # to the opposite side edge_factor = 0.1 for d in dim1, dim2: if not isinstance(d, int): assert False edge_width = box.size[d] * edge_factor faceNormal = [0, 0, 0] cameraBehind = False if point[d] - box.origin[d] < edge_width: faceNormal[d] = -1 cameraBehind = nearPoint[d] - box.origin[d] > 0 if point[d] - box.maximum[d] > -edge_width: faceNormal[d] = 1 cameraBehind = nearPoint[d] - box.maximum[d] < 0 if numpy.dot(faceNormal, mouseVector) > 0 or cameraBehind: # the face adjacent to the clicked edge faces away from the cam # xxxx this is where to allow iso views in face-on angles to grab edges # xxxx also change face highlight node to highlight this area return intersections[1] if len( intersections) > 1 else intersections[0] return point, face
def boxFaceUnderCursor(box, mouseRay): """ Find the nearest face of the given bounding box that intersects with the given mouse ray and return (point, face) """ nearPoint, mouseVector = mouseRay intersections = rayIntersectsBox(box, mouseRay) if intersections is None: return None, None point, face = intersections[0] # if the point is near the edge of the face, and the edge is facing away, # return the away-facing face dim = face.dimension dim1 = (dim + 1) % 3 dim2 = (dim + 2) % 3 # determine if a click was within self.edge_factor of the edge of a selection box side. if so, click through # to the opposite side edge_factor = 0.1 for d in dim1, dim2: if not isinstance(d, int): assert False edge_width = box.size[d] * edge_factor faceNormal = [0, 0, 0] cameraBehind = False if point[d] - box.origin[d] < edge_width: faceNormal[d] = -1 cameraBehind = nearPoint[d] - box.origin[d] > 0 if point[d] - box.maximum[d] > -edge_width: faceNormal[d] = 1 cameraBehind = nearPoint[d] - box.maximum[d] < 0 if numpy.dot(faceNormal, mouseVector) > 0 or cameraBehind: # the face adjacent to the clicked edge faces away from the cam # xxxx this is where to allow iso views in face-on angles to grab edges # xxxx also change face highlight node to highlight this area return intersections[1] if len(intersections) > 1 else intersections[0] return point, face