diff --git a/blender/osci_render/__init__.py b/blender/osci_render/__init__.py new file mode 100644 index 0000000..22b09be --- /dev/null +++ b/blender/osci_render/__init__.py @@ -0,0 +1,206 @@ +bl_info = { + "name": "osci-render", + "author": "James Ball", + "version": (1, 0, 0), + "blender": (3, 1, 2), + "location": "View3D", + "description": "Addon to send frames over to osci-render", + "warning": "Requires a camera and objects", + "wiki_url": "https://github.com/jameshball/osci-render", + "category": "Development", +} + +import bpy +import bmesh +import socket +import json + +HOST = "localhost" +PORT = 51677 + +sock = None + + +class OBJECT_PT_osci_render_settings(bpy.types.Panel): + bl_idname = "OBJECT_PT_osci_render_settings" + bl_label = "osci-render settings" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + + def draw_header(self, context): + layout = self.layout + + def draw(self, context): + global sock + if sock is None: + self.layout.operator("render.osci_render_connect", text="Connect to osci-render") + else: + self.layout.operator("render.osci_render_close", text="Close osci-render connection") + + +class osci_render_connect(bpy.types.Operator): + bl_label = "Connect to osci-render" + bl_idname = "render.osci_render_connect" + bl_description = "Connect to osci-render" + + def execute(self, context): + global sock + if sock is None: + try: + bpy.context.scene.collection["osci_render"] = {} + bpy.context.scene.collection["osci_render"]["seen_objs"] = {} + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((HOST, PORT)) + send_scene_to_osci_render(bpy.context.scene) + except OSError as exp: + sock.close() + sock = None + + return {"FINISHED"} + + +class osci_render_close(bpy.types.Operator): + bl_label = "Close osci-render connection" + bl_idname="render.osci_render_close" + + def execute(self, context): + global sock + if sock is not None: + sock.send("CLOSE\n".encode('utf-8')) + sock.close() + sock = None + + return {"FINISHED"} + + +def is_cyclic(spline): + return spline.use_cyclic_u or spline.use_cyclic_v + + +def append_matrix(object_info, obj): + camera_space = bpy.context.scene.camera.matrix_world.inverted() @ obj.matrix_world + object_info["matrix"] = [camera_space[i][j] for i in range(4) for j in range(4)] + return object_info + + +def send_scene_to_osci_render(scene): + col = bpy.context.scene.collection["osci_render"] + + if sock is not None: + engine_info = {"objects": []} + new_objs = [] + + for obj in bpy.data.objects: + if obj.visible_get(): +# if obj.type == 'MESH': +# object_info = {"name": obj.name} +# if obj.name not in col["seen_objs"]: +# col["seen_objs"][obj.name] = 1 +# new_objs.append(obj.name) + +# mesh = bmesh.new() +# mesh.from_mesh(obj.data) + +# object_info["vertices"] = [] +# # If there are bugs, the vertices here might not match up with the vert.index in edges/faces +# for vert in mesh.verts: +# object_info["vertices"].append({ +# "x": vert.co[0], +# "y": vert.co[1], +# "z": vert.co[2], +# }) + +# object_info["edges"] = [vert.index for edge in mesh.edges for vert in edge.verts] +# object_info["faces"] = [[vert.index for vert in face.verts] for face in mesh.faces] + +# engine_info["objects"].append(append_matrix(object_info, obj)) + if obj.type == 'GPENCIL': + object_info = {"name": obj.name} + strokes = obj.data.layers.active.frames.data.active_frame.strokes + + print("found gpencil!") + print(strokes) + + + object_info["pathVertices"] = [] + for stroke in strokes: + for vert in stroke.points: + object_info["pathVertices"].append({ + "x": vert.co[0], + "y": vert.co[1], + "z": vert.co[2], + }) + # end of path + object_info["pathVertices"].append({ + "x": float("nan"), + "y": float("nan"), + "z": float("nan"), + }) + + engine_info["objects"].append(append_matrix(object_info, obj)) + # elif obj.type == 'CURVE': + # object_info = {"name": obj.name} + # for curve in obj.data.splines: + # if curve.type == 'BEZIER': + # object_info["bezierPoints"] = [] + # points = list(curve.bezier_points) + # if is_cyclic(curve) and len(points) > 0: + # points.append(points[0]) + # + # for point in points: + # for co in [point.co, point.handle_left, point.handle_right]: + # object_info["bezierPoints"].append({ + # "x": co[0], + # "y": co[1], + # "z": co[2], + # }) + # + # engine_info["objects"].append(append_matrix(object_info, obj)) + # elif curve.type == 'POLY': + # object_info["polyPoints"] = [] + # points = list(curve.points) + # if is_cyclic(curve) and len(points) > 0: + # points.append(points[0]) + # + # object_info["polyPoints"] = [{ + # "x": point.co[0], + # "y": point.co[1], + # "z": point.co[2], + # } for point in points] + # + # engine_info["objects"].append(append_matrix(object_info, obj)) + + + + engine_info["focalLength"] = -0.1 * bpy.data.cameras[0].lens + + try: + json_str = json.dumps(engine_info, separators=(',', ':')) + '\n' + sock.sendall(json_str.encode('utf-8')) + except OSError as exc: + # Remove all newly added objects if no connection was made + # so that the object data will be sent on next attempt + for obj_name in new_objs: + col["seen_objs"].pop(obj_name) + + +operations = [OBJECT_PT_osci_render_settings, osci_render_connect, osci_render_close] + + +def register(): + bpy.app.handlers.frame_change_pre.append(send_scene_to_osci_render) + bpy.app.handlers.depsgraph_update_post.append(send_scene_to_osci_render) + for operation in operations: + bpy.utils.register_class(operation) + + +def unregister(): + bpy.app.handlers.frame_change_pre.clear() + bpy.app.handlers.depsgraph_update_post.clear() + for operation in operations.reverse(): + bpy.utils.unregister_class(operation) + + +if __name__ == "__main__": + register() diff --git a/src/main/java/sh/ball/engine/CameraDrawKernel.java b/src/main/java/sh/ball/engine/CameraDrawKernel.java index 1fc71ea..560aea7 100644 --- a/src/main/java/sh/ball/engine/CameraDrawKernel.java +++ b/src/main/java/sh/ball/engine/CameraDrawKernel.java @@ -10,14 +10,16 @@ import sh.ball.shapes.Vector2; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.IntStream; public class CameraDrawKernel extends Kernel { private WorldObject prevObject = null; private List prevObjects = null; + private List prevPaths = null; private float[] vertices; private float[] vertexResult; - private float[] triangles; + private float[] triangles = new float[1]; private float[] matrices = new float[1]; private int[] vertexNums = new int[1]; private float rotationX; @@ -54,38 +56,57 @@ public class CameraDrawKernel extends Kernel { return count; } - public List draw(ObjectSet objects, float focalLength) { + public synchronized List draw(ObjectSet objects, float focalLength) { this.focalLength = focalLength; usingObjectSet = 1; - List objectList = objects.objects.stream().toList(); - if (!objectList.equals(prevObjects)) { - prevObjects = objectList; - List>> vertices = objectList.stream().map(WorldObject::getVertexPath).toList(); - this.vertexNums = vertices.stream().mapToInt( - l -> l.stream() - .map(List::size) - .reduce(0, Integer::sum) + l.size() + if (!objects.objects.equals(prevObjects) || !objects.pathObjects.equals(prevPaths)) { + prevObjects = objects.objects; + prevPaths = objects.pathObjects; + List>> vertices = objects.objects.stream().map(WorldObject::getVertexPath).toList(); + this.vertexNums = IntStream.concat( + vertices.stream().mapToInt( + l -> l.stream() + .map(List::size) + .reduce(0, Integer::sum) + l.size() + ), + objects.pathObjects.stream().mapToInt( + arr -> arr.length + ) ).toArray(); int numVertices = Arrays.stream(vertexNums).sum(); this.vertices = new float[numVertices * 3]; this.vertexResult = new float[numVertices * 2]; - this.matrices = new float[vertices.size() * 16]; - List triangles = objectList.stream().map(WorldObject::getTriangles).toList(); - int numTriangles = triangles.stream().map(arr -> arr.length).reduce(0, Integer::sum); - this.triangles = new float[numTriangles]; - int offset = 0; - for (float[] triangleArray : triangles) { - System.arraycopy(triangleArray, 0, this.triangles, offset, triangleArray.length); - offset += triangleArray.length; + this.matrices = new float[(vertices.size() + objects.pathObjects.size()) * 16]; + if (!objects.objects.isEmpty()) { + List triangles = objects.objects.stream().map(WorldObject::getTriangles).toList(); + int numTriangles = triangles.stream().map(arr -> arr.length).reduce(0, Integer::sum); + this.triangles = new float[numTriangles]; + int offset = 0; + for (float[] triangleArray : triangles) { + System.arraycopy(triangleArray, 0, this.triangles, offset, triangleArray.length); + offset += triangleArray.length; + } } int count = 0; for (List> vertexList : vertices) { count = initialiseVertices(count, vertexList); } + for (Vector3[] vectors : objects.pathObjects) { + for (Vector3 vertex : vectors) { + this.vertices[3 * count] = (float) vertex.x; + this.vertices[3 * count + 1] = (float) vertex.y; + this.vertices[3 * count + 2] = (float) vertex.z; + count++; + } + } } int offset = 0; - for (float[] matrix : objects.cameraSpaceMatrices) { + for (float[] matrix : objects.objectMatrices) { + System.arraycopy(matrix, 0, this.matrices, offset, matrix.length); + offset += matrix.length; + } + for (float[] matrix : objects.pathMatrices) { System.arraycopy(matrix, 0, this.matrices, offset, matrix.length); offset += matrix.length; } @@ -134,7 +155,11 @@ public class CameraDrawKernel extends Kernel { e.printStackTrace(); } - execute(Range.create(roundUp(vertices.length / 3, maxGroupSize), maxGroupSize)); + for (int i = 0; i < vertices.length / 3; i++) { + processVertex(i); + } + + //execute(Range.create(roundUp(vertices.length / 3, maxGroupSize), maxGroupSize)); List linesList = new ArrayList<>(); @@ -167,9 +192,7 @@ public class CameraDrawKernel extends Kernel { return round + multiple - remainder; } - @Override - public void run() { - int i = getGlobalId(); + private void processVertex(int i) { float EPSILON = 0.00001f; float x1 = vertices[3 * i]; @@ -214,10 +237,11 @@ public class CameraDrawKernel extends Kernel { int obj = -1; for (int j = 0; j < vertexNums.length; j++) { totalVertices += vertexNums[j]; - if (totalVertices >= i && obj == -1) { + if (totalVertices > i && obj == -1) { obj = j; } } + // TODO: This matrix multiplication somehow causes a NaN rotatedX = matrices[16 * obj] * x1 + matrices[16 * obj + 1] * y1 + matrices[16 * obj + 2] * z1 + matrices[16 * obj + 3]; rotatedY = matrices[16 * obj + 4] * x1 + matrices[16 * obj + 5] * y1 + matrices[16 * obj + 6] * z1 + matrices[16 * obj + 7]; rotatedZ = matrices[16 * obj + 8] * x1 + matrices[16 * obj + 9] * y1 + matrices[16 * obj + 10] * z1 + matrices[16 * obj + 11]; @@ -328,6 +352,11 @@ public class CameraDrawKernel extends Kernel { } } + @Override + public void run() { + processVertex(getGlobalId()); + } + float crossX(float x0, float x1, float x2, float y0, float y1, float y2) { return x1 * y2 - y1 * x2; } diff --git a/src/main/java/sh/ball/engine/ObjectServer.java b/src/main/java/sh/ball/engine/ObjectServer.java index bd890e6..4a8f952 100644 --- a/src/main/java/sh/ball/engine/ObjectServer.java +++ b/src/main/java/sh/ball/engine/ObjectServer.java @@ -8,7 +8,6 @@ import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.util.*; -import java.util.stream.Collectors; public class ObjectServer implements Runnable { @@ -41,16 +40,33 @@ public class ObjectServer implements Runnable { } EngineInfo info = gson.fromJson(json, EngineInfo.class); + List objectsToRender = new ArrayList<>(); + List objectMatrices = new ArrayList<>(); + + List pathObjects = new ArrayList<>(); + List pathMatrices = new ArrayList<>(); + + Set currentObjects = new HashSet<>(); + for (ObjectInfo obj : info.objects) { + currentObjects.add(obj.name); if (!objects.containsKey(obj.name)) { - objects.put(obj.name, new WorldObject(obj.vertices, obj.edges, obj.faces)); + if (obj.vertices != null) { + objects.put(obj.name, new WorldObject(obj.vertices, obj.edges, obj.faces)); + } + } + if (obj.pathVertices == null) { + objectsToRender.add(objects.get(obj.name)); + objectMatrices.add(obj.matrix); + } else { + pathObjects.add(obj.pathVertices); + pathMatrices.add(obj.matrix); } } - Set currentObjects = Arrays.stream(info.objects).map(obj -> obj.name).collect(Collectors.toSet()); objects.entrySet().removeIf(obj -> !currentObjects.contains(obj.getKey())); - objectSet.setObjects(objects.values(), Arrays.stream(info.objects).map(obj -> obj.matrix).toList(), info.focalLength); + objectSet.setObjects(objectsToRender, objectMatrices, pathObjects, pathMatrices, info.focalLength); } disableRendering.run(); } @@ -79,6 +95,7 @@ public class ObjectServer implements Runnable { private static class ObjectInfo { private String name; private Vector3[] vertices; + private Vector3[] pathVertices; private int[] edges; private int[][] faces; // Camera space matrix diff --git a/src/main/java/sh/ball/engine/ObjectSet.java b/src/main/java/sh/ball/engine/ObjectSet.java index 3df436f..2e9cc2e 100644 --- a/src/main/java/sh/ball/engine/ObjectSet.java +++ b/src/main/java/sh/ball/engine/ObjectSet.java @@ -4,7 +4,6 @@ import sh.ball.audio.FrameSource; import sh.ball.shapes.Shape; import sh.ball.shapes.Vector2; -import java.util.Collection; import java.util.List; import java.util.Objects; @@ -12,16 +11,20 @@ public class ObjectSet implements FrameSource> { private final CameraDrawKernel kernel = new CameraDrawKernel(); - public Collection objects; - public Collection cameraSpaceMatrices; + public List objects; + public List objectMatrices; + public List pathObjects; + public List pathMatrices; private boolean active = true; private float focalLength; public ObjectSet() {} - public void setObjects(Collection objects, Collection matrices, float focalLength) { + public synchronized void setObjects(List objects, List matrices, List pathObjects, List pathMatrices, float focalLength) { this.objects = objects; - this.cameraSpaceMatrices = matrices; + this.objectMatrices = matrices; + this.pathObjects = pathObjects; + this.pathMatrices = pathMatrices; this.focalLength = focalLength; } @@ -34,19 +37,21 @@ public class ObjectSet implements FrameSource> { if (!Objects.equals(objects, objectSet.objects)) return false; - return Objects.equals(cameraSpaceMatrices, objectSet.cameraSpaceMatrices); + return Objects.equals(objectMatrices, objectSet.objectMatrices); } @Override public int hashCode() { int result = objects != null ? objects.hashCode() : 0; - result = 31 * result + (cameraSpaceMatrices != null ? cameraSpaceMatrices.hashCode() : 0); + result = 31 * result + (objectMatrices != null ? objectMatrices.hashCode() : 0); return result; } @Override - public List next() { - if (objects == null || cameraSpaceMatrices == null) { + public synchronized List next() { + if ((objects == null || objectMatrices == null || objects.isEmpty() || objectMatrices.isEmpty()) + && (pathObjects == null || pathMatrices == null || pathObjects.isEmpty() || pathMatrices.isEmpty())) { + System.out.println("nothing to draw!"); return List.of(new Vector2()); } return kernel.draw(this, focalLength);