3D line art engine.

Related tags

Images tools ln
Overview

ln The 3D Line Art Engine

ln is a vector-based 3D renderer written in Go. It is used to produce 2D vector graphics (think SVGs) depicting 3D scenes.

The output of an OpenGL pipeline is a rastered image. The output of ln is a set of 2D vector paths.

Examples

Motivation

I created this so I could plot 3D drawings with my Makeblock XY Plotter.

Here's one of my drawings from the plotter...

Example

Installation

go get github.com/fogleman/ln/ln

Features

  • Primitives
    • Sphere
    • Cube
    • Triangle
    • Cylinder
    • 3D Functions
  • Triangle Meshes
    • OBJ & STL
  • Vector-based "Texturing"
  • CSG (Constructive Solid Geometry) Operations
    • Intersection
    • Difference
    • Union
  • Output to PNG or SVG

How it Works

To understand how ln works, it's useful to start with the Shape interface:

type Shape interface {
	Paths() Paths
	Intersect(Ray) Hit
	Contains(Vector, float64) bool
	BoundingBox() Box
	Compile()
}

Each shape must provide some Paths which are 3D polylines on the surface of the solid. Ultimately anything drawn in the final image is based on these paths. These paths can be anything. For a sphere they could be lat/lng grid lines, a triangulated-looking surface, dots on the surface, etc. This is what we call vector-based texturing. Each built-in Shape ships with a default Paths function (e.g. a Cube simply draws the outline of a cube) but you can easily provide your own.

Each shape must also provide an Intersect method that lets the engine test for ray-solid intersection. This is how the engine knows what is visible to the camera and what is hidden.

All of the Paths are chopped up to some granularity and each point is tested by shooting a ray toward the camera. If there is no intersection, that point is visible. If there is an intersection, it is hidden and will not be rendered.

The visible points are then transformed into 2D space using transformation matrices. The result can then be rendered as PNG or SVG.

The Contains method is only needed for CSG (Constructive Solid Geometry) operations.

Hello World: A Single Cube

The Code

package main

import "github.com/fogleman/ln/ln"

func main() {
	// create a scene and add a single cube
	scene := ln.Scene{}
	scene.Add(ln.NewCube(ln.Vector{-1, -1, -1}, ln.Vector{1, 1, 1}))

	// define camera parameters
	eye := ln.Vector{4, 3, 2}    // camera position
	center := ln.Vector{0, 0, 0} // camera looks at
	up := ln.Vector{0, 0, 1}     // up direction

	// define rendering parameters
	width := 1024.0  // rendered width
	height := 1024.0 // rendered height
	fovy := 50.0     // vertical field of view, degrees
	znear := 0.1     // near z plane
	zfar := 10.0     // far z plane
	step := 0.01     // how finely to chop the paths for visibility testing

	// compute 2D paths that depict the 3D scene
	paths := scene.Render(eye, center, up, width, height, fovy, znear, zfar, step)

	// render the paths in an image
	paths.WriteToPNG("out.png", width, height)

	// save the paths as an svg
	paths.WriteToSVG("out.svg", width, height)
}

The Output

Cube

Custom Texturing

Suppose we want to draw cubes with vertical stripes on their sides, as shown in the skyscrapers example above. We can just define a new type and override the Paths() function.

type StripedCube struct {
	ln.Cube
	Stripes int
}

func (c *StripedCube) Paths() ln.Paths {
	var paths ln.Paths
	x1, y1, z1 := c.Min.X, c.Min.Y, c.Min.Z
	x2, y2, z2 := c.Max.X, c.Max.Y, c.Max.Z
	for i := 0; i <= c.Stripes; i++ {
		p := float64(i) / float64(c.Stripes)
		x := x1 + (x2-x1)*p
		y := y1 + (y2-y1)*p
		paths = append(paths, ln.Path{{x, y1, z1}, {x, y1, z2}})
		paths = append(paths, ln.Path{{x, y2, z1}, {x, y2, z2}})
		paths = append(paths, ln.Path{{x1, y, z1}, {x1, y, z2}})
		paths = append(paths, ln.Path{{x2, y, z1}, {x2, y, z2}})
	}
	return paths
}

Now StripedCube instances can be added to the scene.

Constructive Solid Geometry (CSG)

You can easily construct complex solids using Intersection, Difference, Union.

shape := ln.NewDifference(
	ln.NewIntersection(
		ln.NewSphere(ln.Vector{}, 1),
		ln.NewCube(ln.Vector{-0.8, -0.8, -0.8}, ln.Vector{0.8, 0.8, 0.8}),
	),
	ln.NewCylinder(0.4, -2, 2),
	ln.NewTransformedShape(ln.NewCylinder(0.4, -2, 2), ln.Rotate(ln.Vector{1, 0, 0}, ln.Radians(90))),
	ln.NewTransformedShape(ln.NewCylinder(0.4, -2, 2), ln.Rotate(ln.Vector{0, 1, 0}, ln.Radians(90))),
)

This is (Sphere & Cube) - (Cylinder | Cylinder | Cylinder).

Unfortunately, it's difficult to compute the joint formed at the boundaries of these combined shapes, so sufficient texturing is needed on the original solids for a decent result.

Example

Comments
  • Go noobz not supported

    Go noobz not supported

    D:\src\go>md bin D:\src\go>md src D:\src\go>md pkg ... D:\src\go>set GOPATH=D:\src\go D:\src\go>go get github.com/fogleman/ln package github.com/fogleman/ln: no buildable Go source files in D:\src\go\src\github.com\fogleman\ln

    If applicable, would you consider adding missing steps to readme.md to support this use case: I don't want to learn Go from scratch, I just want to use your app to create svg files?

    opened by polycopter 5
  • ln/path: use Draw2D instead of Cairo for a pure-Go build

    ln/path: use Draw2D instead of Cairo for a pure-Go build

    This partially addresses fogleman/ln#3, with the benefit of not requiring OpenGL. I'm sure the performance is suboptimal, but this was so simple I couldn't resist.

    opened by fogleman 4
  • Error: Failed to download resource

    Error: Failed to download resource "libpng"

    curl: (22) The requested URL returned error: 404 Not Found
    Error: Failed to download resource "libpng"
    Download failed: https://dl.bintray.com/homebrew/mirror/libpng-1.6.18.tar.xz
    
    opened by xiaoxu193 2
  • Malloc deadlock in rendering large OBJ

    Malloc deadlock in rendering large OBJ

    Running

    package main
    
    import "github.com/fogleman/ln/ln"
    
    func main() {
        tree, err := ln.LoadOBJ("tree.obj")
        if err != nil {
            panic(err)
        }
    
        scene := ln.Scene{}
        scene.Add(tree)
    
        // define camera parameters
        eye := ln.Vector{4, 3, 2}    // camera position
        center := ln.Vector{0, 0, 0} // camera looks at
        up := ln.Vector{0, 0, 1}     // up direction
    
        // define rendering parameters
        width := 1024.0  // rendered width
        height := 1024.0 // rendered height
        fovy := 50.0     // vertical field of view, degrees
        znear := 0.1     // near z plane
        zfar := 10.0     // far z plane
        step := 0.01     // how finely to chop the paths for visibility testing
    
        // compute 2D paths that depict the 3D scene
        paths := scene.Render(eye, center, up, width, height, fovy, znear, zfar, step)
    
        paths.WriteToPNG("out.png", width, height)
    
        // save the paths as an svg
        paths.WriteToSVG("out.svg", width, height)
    }
    

    with tree.obj being a large OBJ file (15MB) gives

    panic: fatal error: malloc deadlock
    
    runtime stack:
    runtime.throw(0x151fc0, 0xf)
            /home/pi/golang/go/src/runtime/panic.go:527 +0x78
    runtime.mallocgc(0x21, 0x0, 0x3, 0x1864a8)
            /home/pi/golang/go/src/runtime/malloc.go:513 +0x13c
    runtime.rawstring(0x21, 0x0, 0x0, 0x0, 0x0, 0x0)
            /home/pi/golang/go/src/runtime/string.go:264 +0x64
    runtime.rawstringtmp(0x0, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0)
            /home/pi/golang/go/src/runtime/string.go:107 +0xb4
    runtime.concatstrings(0x0, 0x45f57a38, 0x2, 0x2, 0x0, 0x0)
            /home/pi/golang/go/src/runtime/string.go:48 +0x1e4
    runtime.concatstring2(0x0, 0x1523c0, 0xf, 0x1556e8, 0x12, 0x0, 0x0)
            /home/pi/golang/go/src/runtime/string.go:58 +0x58
    runtime.(*errorString).Error(0x569a4010, 0x0, 0x0)
            <autogenerated>:2 +0xd8
    runtime.printany(0x12b5b0, 0x569a4010)
            /home/pi/golang/go/src/runtime/error.go:75 +0x140
    runtime.gopanic(0x12b5b0, 0x569a4010)
            /home/pi/golang/go/src/runtime/panic.go:352 +0x5c
    runtime.panicindex()
            /home/pi/golang/go/src/runtime/panic.go:12 +0x48
    runtime.mHeap_Grow(0x1ed0e0, 0x8, 0x0)
            /home/pi/golang/go/src/runtime/mheap.go:647 +0x25c
    runtime.mHeap_AllocSpanLocked(0x1ed0e0, 0x1, 0x0)
            /home/pi/golang/go/src/runtime/mheap.go:532 +0x6c8
    runtime.mHeap_Alloc_m(0x1ed0e0, 0x1, 0xd, 0x0, 0x5d26c)
            /home/pi/golang/go/src/runtime/mheap.go:425 +0x23c
    runtime.mHeap_Alloc.func1()
            /home/pi/golang/go/src/runtime/mheap.go:484 +0x40
    runtime.systemstack(0x45f57be4)
            /home/pi/golang/go/src/runtime/asm_arm.s:256 +0xa8
    runtime.mHeap_Alloc(0x1ed0e0, 0x1, 0xd, 0x100, 0x1)
            /home/pi/golang/go/src/runtime/mheap.go:485 +0x58
    runtime.mCentral_Grow(0x1f20d0, 0x0)
            /home/pi/golang/go/src/runtime/mcentral.go:190 +0xb0
    runtime.mCentral_CacheSpan(0x1f20d0, 0x43ada1ac)
            /home/pi/golang/go/src/runtime/mcentral.go:86 +0x5e0
    runtime.mCache_Refill(0x4685925c, 0xd, 0x43ada1ac)
            /home/pi/golang/go/src/runtime/mcache.go:118 +0xd4
    runtime.mallocgc.func2()
            /home/pi/golang/go/src/runtime/malloc.go:611 +0x28
    runtime.systemstack(0x569b7400)
            /home/pi/golang/go/src/runtime/asm_arm.s:242 +0x80
    runtime.mstart()
            /home/pi/golang/go/src/runtime/proc1.go:674
    
    goroutine 1 [running]:
    runtime.systemstack_switch()
            /home/pi/golang/go/src/runtime/asm_arm.s:187 +0x4 fp=0x5ec8b90c sp=0x5ec8b908
    runtime.mallocgc(0xc0, 0x0, 0x3, 0x8)
            /home/pi/golang/go/src/runtime/malloc.go:612 +0x840 fp=0x5ec8b978 sp=0x5ec8b90c
    runtime.rawmem(0xc0, 0x30)
            /home/pi/golang/go/src/runtime/malloc.go:788 +0x30 fp=0x5ec8b98c sp=0x5ec8b978
    runtime.growslice(0x136148, 0x768e44e0, 0x4, 0x4, 0x5, 0x0, 0x0, 0x0)
            /home/pi/golang/go/src/runtime/slice.go:92 +0x230 fp=0x5ec8b9c4 sp=0x5ec8b98c
    github.com/fogleman/ln/ln.Path.Chop(0x59a9e840, 0x2, 0x2, 0x47ae147b, 0x3f847ae1, 0x0, 0x0, 0x0)
            /home/pi/golang/projects/src/github.com/fogleman/ln/ln/path.go:41 +0x434 fp=0x5ec8bb28 sp=0x5ec8b9c4
    github.com/fogleman/ln/ln.Paths.Chop(0x61e14000, 0xe29c8, 0xe2aaa, 0x47ae147b, 0x3f847ae1, 0x0, 0x0, 0x0)
            /home/pi/golang/projects/src/github.com/fogleman/ln/ln/path.go:131 +0xc0 fp=0x5ec8bb88 sp=0x5ec8bb28
    github.com/fogleman/ln/ln.(*Scene).Render(0x576ac340, 0x0, 0x40100000, 0x0, 0x40080000, 0x0, 0x40000000, 0x0, 0x0, 0x0, ...)
            /home/pi/golang/projects/src/github.com/fogleman/ln/ln/scene.go:47 +0x398 fp=0x5ec8be7c sp=0x5ec8bb88
    main.main()
            /home/pi/golang/projects/src/github.com/tdewolff/tree/main.go:28 +0x2e0 fp=0x5ec8bf9c sp=0x5ec8be7c
    runtime.main()
            /home/pi/golang/go/src/runtime/proc.go:111 +0x2b4 fp=0x5ec8bfc4 sp=0x5ec8bf9c
    runtime.goexit()
            /home/pi/golang/go/src/runtime/asm_arm.s:1036 +0x4 fp=0x5ec8bfc4 sp=0x5ec8bfc4
    
    goroutine 17 [syscall, 2 minutes, locked to thread]:
    runtime.goexit()
            /home/pi/golang/go/src/runtime/asm_arm.s:1036 +0x4
    

    on a Raspberry Pi 2B, Debian. The OBJ file I used is here: http://pi.tacodewolff.nl:8080/tree.obj

    opened by tdewolff 1
  • Requires cairo

    Requires cairo

    fyi https://github.com/fogleman/ln/blob/73feb4b1f52c86fb2a25e8e82a0fc551dc5cf038/ln/path.go#L8 - requires a project which requires the cairo library, meaning there are external requirements. Would you take a PR for the docs?

    opened by ukd1 1
  • Cannot find cairo-pdf.h (fixed)

    Cannot find cairo-pdf.h (fixed)

    In case someone else has this problem: after running go get github.com/ungerik/go-cairo I got an error about cairo-pdf.h being not found; this was from the include cairo-pdf.h (among other headers), where the "include" no longer worked properly because of where cairo's files were held (recent Mac OX has an Xcode path instead of the /usr/include which causes problems).

    Short story solution: installed some Xcode "command line developer tools" using xcode-select --install from the terminal. No more error. Not sure if this is the best way, but see here.

    edit: called "command line developer tools"

    opened by JRov 0
  • Gravitional waves example

    Gravitional waves example

    Hello,

    I really like the ln library and I've been using it with a drawbot I've built. Thank you!

    I am trying to generate the gravitational waves example from the image in the Readme. I've gone through all of the examples and I can't seem to figure out how to generate that. I've successfully generated run all of the other examples. Can you elaborate on how that specifically is done?

    opened by speckone 2
  • Orthographic Errors

    Orthographic Errors

    Hi, I'm trying to do some tests using an orthographic matrix, but I seem to be getting some errors in the ray intersection. I don't know if I've set something up wrong.

    eye := ln.Vector{-3, 4, 3}   // camera position
    center := ln.Vector{0, 0, 0} // camera looks at
    up := ln.Vector{0, 1, 0}     // up direction
    znear := 0.1 // near z plane
    zfar := 10.0 // far z plane
    step := 0.01 // how finely to chop the paths for visibility testing
    matrix := ln.LookAt(eye, center, up).Orthographic(-1, 1, -1, 1, znear, zfar)
    paths := scene.RenderWithMatrix(matrix, eye, width, height, step)
    

    As you can see, edges are clipped before they go behind objects.

    screen shot 2018-01-07 at 4 32 53 am

    opened by scisci 5
Owner
Michael Fogleman
Software Engineer at Formlabs
Michael Fogleman
generativeart is a Go package to generate many kinds of generative art.

generativeart is a Go package to generate many kinds of generative art. The goal is to collect some excellent generative art (implemented in R or Processing), and rewrite them in Go again

null 825 Dec 29, 2022
Generate high-quality triangulated art from images.

An iterative algorithm to generate high quality triangulated images.

null 53 May 26, 2021
A cross-platform tool to convert images into ascii art and print them on the console

A cross-platform tool to convert images into ascii art and print them on the console

Zoraiz Hassan 1.1k Dec 30, 2022
Convert images to computer generated art using delaunay triangulation.

▲ Triangle is a tool for generating triangulated image using delaunay triangulation. It takes a source image and converts it to an abstract image comp

Endre Simo 2k Dec 29, 2022
Ascii-art-web

ASCII-ART-WEB Author: Alika96 How to run Run the following commands: For building an image: docker image build -t ascii-art-web-docker . For showing i

null 0 Dec 13, 2021
API for generate image to ASCII Art

ASCII API Generate ASCII art from image. You can try this API here: ascii.projec

Sh4yn 5 Jul 1, 2022
Human-friendly Go module that builds and prints directory trees using ASCII art

Human-friendly Go module that builds and prints directory trees using ASCII art.

Vadym Borodin 5 Oct 11, 2022
A simple javascript website that takes user input, queries a Go based backend which then creates ascii art and sends it back to the frontend

A simple javascript website that takes user input, queries a Go based backend which then creates ascii art and sends it back to the frontend. Finally the site displays the ascii art and offers the option to download as multiple file types.

null 0 Jan 7, 2022
A command to output longified ascii art.

longify A command to output longified ascii art. Inspired by Tweet from @sheepla: https://twitter.com/Sheeeeepla/status/1522199846870196225 Installati

syumai 11 Sep 12, 2022
Virtual Universe 3D Engine

Vu Vu (Virtual Universe) is a 3D engine based on the modern programming language Go (Golang). Vu is composed of packages, detailed in GoDoc, and brief

null 199 Dec 9, 2022
A phoenix Chain client based on the go-ethereum fork,the new PoS consensus engine is based on the VRF algorithm.

Phoenix Official Golang implementation of the Phoenix protocol. !!!The current version is for testing and developing purposes only!!! Building the sou

null 9 Aug 18, 2022
An extensive, fast, and accurate command-line image dithering tool.

didder is an extensive, fast, and accurate command-line image dithering tool. It is designed to work well for both power users as well as pipeline scripting. It is backed by my dithering library, and is unique in its correctness and variety of dithering algorithms.

makeworld 169 Dec 31, 2022
This command line converts .html file into .html with images embed.

embed-html This command line converts .html file into .html with images embed. Install > go get github.com/gonejack/embed-html Usage > embed-html *.ht

会有猫的 1 Oct 6, 2022
3D line art engine.

ln The 3D Line Art Engine ln is a vector-based 3D renderer written in Go. It is used to produce 2D vector graphics (think SVGs) depicting 3D scenes. T

Michael Fogleman 3.1k Dec 28, 2022
:art: Contextual fmt inspired by bootstrap color classes

Cfmt Contextual fmt It provides contextual formatting functions that have nearly identical usage of the fmt package. The ideas were borrowed from boot

MinJae Kwon 92 Jan 7, 2023
:pushpin: State of the art point location and neighbour finding algorithms for region quadtrees, in Go

Region quadtrees in Go Region quadtrees and efficient neighbour finding techniques in Go Go-rquad proposes various implementations of region quadtrees

Aurélien Rainone 122 Dec 13, 2022
generativeart is a Go package to generate many kinds of generative art.

generativeart is a Go package to generate many kinds of generative art. The goal is to collect some excellent generative art (implemented in R or Processing), and rewrite them in Go again

null 825 Dec 29, 2022
Generate high-quality triangulated art from images.

An iterative algorithm to generate high quality triangulated images.

null 53 May 26, 2021
A cross-platform tool to convert images into ascii art and print them on the console

A cross-platform tool to convert images into ascii art and print them on the console

Zoraiz Hassan 1.1k Dec 30, 2022
Convert images to computer generated art using delaunay triangulation.

▲ Triangle is a tool for generating triangulated image using delaunay triangulation. It takes a source image and converts it to an abstract image comp

Endre Simo 2k Dec 29, 2022