func createBuffer() *glh.MeshBuffer { const N = 128 const f = 0.025 pos := make([]float64, N*2) clr := make([]float64, N*4) for i := 0; i < N; i += 1 { pos[2*i+0] = math.Cos(f * 2 * math.Pi * 2 * float64(i) / N) pos[2*i+1] = math.Sin(f * 2 * math.Pi * 2 * float64(i) / N) clr[4*i+0] = 0.2 clr[4*i+1] = 0.75 clr[4*i+2] = 0.9 clr[4*i+3] = 0.5 * (1 - float64(i)/float64(N-1)) } // Create a mesh buffer with the given attributes. mb := glh.NewMeshBuffer( glh.RenderArrays, glh.NewPositionAttr(2, gl.DOUBLE, gl.STATIC_DRAW), glh.NewColorAttr(4, gl.DOUBLE, gl.STATIC_DRAW), ) // Add the mesh to the buffer. mb.Add(pos, clr) return mb }
func createMeshBuffer() *glh.MeshBuffer { mb := glh.NewMeshBuffer( glh.RenderBuffered, glh.NewIndexAttr(1, gl.UNSIGNED_SHORT, gl.STATIC_DRAW), // 2 INTEGER values should be enough, but // just to keep things simpler // let's work with 3d vertex instead of 2d // it's easier to find help that way glh.NewPositionAttr(3, gl.FLOAT, gl.STATIC_DRAW), // At this moment, let's use colors instead of textures glh.NewColorAttr(3, gl.UNSIGNED_BYTE, gl.DYNAMIC_DRAW)) return mb }
// createBuffer creates a mesh buffer and fills it with data. func createBuffer() *glh.MeshBuffer { // Create a mesh buffer with the given attributes. mb := glh.NewMeshBuffer( glh.RenderBuffered, // 1 index per vertex. glh.NewIndexAttr(1, gl.UNSIGNED_SHORT, gl.STATIC_DRAW), // Vertex positions have 2 components (x, y). // These will never be changing, so mark them as static. glh.NewPositionAttr(2, gl.INT, gl.STATIC_DRAW), // Colors have 3 components (r, g, b). // These will be changing, so make them dynamic. glh.NewColorAttr(3, gl.UNSIGNED_BYTE, gl.DYNAMIC_DRAW), ) // Define components for a simple, coloured quad. idx := []uint16{0, 1, 2, 3} pos := []int32{0, 0, 0, 0, 0, 0, 0, 0} clr := []uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} for y := 0; y < Rows; y++ { py := int32(CellHeight * y) pos[1] = py + 1 pos[3] = py + 1 pos[5] = py + CellHeight pos[7] = py + CellHeight for x := 0; x < Cols; x++ { px := int32(CellWidth * x) pos[0] = px + 1 pos[2] = px + CellWidth pos[4] = px + CellWidth pos[6] = px + 1 palette := colors[rng.Int31n(6)] setColor(clr, palette[0], palette[1], palette[2]) mb.Add(idx, pos, clr) } } return mb }
func createBuffer() *glh.MeshBuffer { // We create as few vertices as possible. // Manually building a cube would require 24 vertices. Many of which // are duplicates. All we have to define here, is the 8 unique ones // necessary to construct each face of the cube. pos := []float32{ 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, } a := float32(0.02) // Each vertex comes with its own colour. clr := []float32{ 1, 0, 0, a, 0, 1, 0, a, 0, 0, 1, a, 1, 0, 1, a, 1, 1, 0, a, 0, 1, 1, a, 1, 1, 1, a, 0, 0, 0, a, } // These are the indices into the position and color lists. // They tell the GPU which position/color pair to use in order to construct // the whole cube. As can be seen, all elements are repeated multiple // times to create the correct layout. For large meshes with many duplicate // vertices, this can save a sizable amount of storage space. idx := []byte{ 0, 1, 2, 3, 4, 5, 6, 7, 3, 2, 5, 4, 7, 6, 1, 0, 2, 1, 6, 5, 0, 3, 4, 7, } // Create a mesh buffer with the given attributes. mb := glh.NewMeshBuffer( glh.RenderBuffered, // Indices. glh.NewIndexAttr(1, gl.UNSIGNED_BYTE, gl.STATIC_DRAW), // Vertex positions have 3 components (x, y, z). glh.NewPositionAttr(3, gl.FLOAT, gl.STATIC_DRAW), // Colors have 4 components (r, g, b, a). glh.NewColorAttr(4, gl.FLOAT, gl.STATIC_DRAW), ) // Add the mesh to the buffer. mb.Add(idx, pos, clr) return mb }
// Create the glh.MeshBufer func createBuffer() *glh.MeshBuffer { if len(scene.Mesh[0].Colors) == 0 { // just add some colors to it. assimp.RandomColor(scene.Mesh[0]) } fmesh := assimp.NewFlatMesh(scene.Mesh[0]) println("FMesh vertex count: ", len(fmesh.Vertex)) var idxAttr *glh.Attr switch sz := len(fmesh.Index); { case sz < int(assimp.ByteSize): idxAttr = glh.NewIndexAttr(1, gl.UNSIGNED_BYTE, gl.STATIC_DRAW) case sz < int(assimp.ShortSize): idxAttr = glh.NewIndexAttr(1, gl.UNSIGNED_SHORT, gl.STATIC_DRAW) default: idxAttr = glh.NewIndexAttr(1, gl.UNSIGNED_INT, gl.STATIC_DRAW) } idxAttr = glh.NewIndexAttr(1, gl.UNSIGNED_INT, gl.STATIC_DRAW) // Create a mesh buffer with the given attributes. mb := glh.NewMeshBuffer( glh.RenderBuffered, // Indices. idxAttr, // Vertex positions have 3 components (x, y, z). glh.NewPositionAttr(3, gl.FLOAT, gl.STATIC_DRAW), // Colors have 4 components (r, g, b, a). glh.NewColorAttr(4, gl.FLOAT, gl.STATIC_DRAW), ) // Add the mesh to the buffer. mb.Add(fmesh.Index, fmesh.Vertex, fmesh.Color) return mb }
func (sg *OpenGLGraphics) Scatter(points []chart.EPoint, plotstyle chart.PlotStyle, style chart.Style) { //log.Panicf("Unimplemented: %s", whoami()) //chart.GenericScatter(sg, points, plotstyle, style) // TODO: Implement error bars/symbols buffer := glh.NewMeshBuffer(glh.RenderArrays, glh.NewPositionAttr(2, gl.DOUBLE, gl.STATIC_DRAW), glh.NewColorAttr(3, gl.UNSIGNED_INT, gl.STATIC_DRAW)) // var vertices glh.ColorVertices positions := make([]float64, len(points)*2) colors := make([]uint32, len(points)*3) for _, p := range points { r, g, b, _ := style.LineColor.RGBA() colors = append(colors, r, g, b) positions = append(positions, p.X, p.Y) // vertices.Add(glh.ColorVertex{ // glh.MkRGBA(style.LineColor), // glh.Vertex{float32(p.X), float32(p.Y)}}) } buffer.Add() if style.LineWidth != 0 { gl.LineWidth(float32(style.LineWidth)) } else { gl.LineWidth(1) } glh.With(glh.Attrib{gl.ENABLE_BIT | gl.COLOR_BUFFER_BIT}, func() { gl.Enable(gl.BLEND) gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) buffer.Render(gl.LINE_STRIP) }) /*********************************************** // First pass: Error bars ebs := style ebs.LineColor, ebs.LineWidth, ebs.LineStyle = ebs.FillColor, 1, chart.SolidLine if ebs.LineColor == "" { ebs.LineColor = "#404040" } if ebs.LineWidth == 0 { ebs.LineWidth = 1 } for _, p := range points { xl, yl, xh, yh := p.BoundingBox() // fmt.Printf("Draw %d: %f %f-%f\n", i, p.DeltaX, xl,xh) if !math.IsNaN(p.DeltaX) { sg.Line(int(xl), int(p.Y), int(xh), int(p.Y), ebs) } if !math.IsNaN(p.DeltaY) { sg.Line(int(p.X), int(yl), int(p.X), int(yh), ebs) } } // Second pass: Line if (plotstyle&chart.PlotStyleLines) != 0 && len(points) > 0 { path := fmt.Sprintf("M %d,%d", int(points[0].X), int(points[0].Y)) for i := 1; i < len(points); i++ { path += fmt.Sprintf("L %d,%d", int(points[i].X), int(points[i].Y)) } st := linestyle(style) sg.svg.Path(path, st) } // Third pass: symbols if (plotstyle&chart.PlotStylePoints) != 0 && len(points) != 0 { for _, p := range points { sg.Symbol(int(p.X), int(p.Y), style) } } ****************************************************/ }
func (block *Block) GenerateVertices() *glh.MeshBuffer { width := uint64(len(block.display_active_pages)+1) * *PAGE_SIZE vc := glh.NewMeshBuffer( glh.RenderArrays, glh.NewPositionAttr(2, gl.FLOAT, gl.STATIC_DRAW), glh.NewColorAttr(3, gl.UNSIGNED_BYTE, gl.STATIC_DRAW), ) var stack_depth int = len(block.context_records) vertex := []float32{0, 0} colour := []uint8{0, 0, 0} x, y := &vertex[0], &vertex[1] r, g, b := &colour[0], &colour[1], &colour[2] vertices := make([]float32, 0, block.nrecords*2) colours := make([]uint8, 0, block.nrecords*3) for pos := int64(0); pos < int64(block.nrecords); pos++ { if pos < 0 { continue } rec := &block.records[pos] if rec.Type == MEMA_ACCESS { // take it } else if rec.Type == MEMA_FUNC_ENTER { stack_depth++ *x = 2 + float32(stack_depth)/80. *y = float32(pos) //int64(len(*vc))) *r, *g, *b = 64, 64, 255 vertices = append(vertices, *x, *y) colours = append(colours, *r, *g, *b) //vc.Add(vertex, colour) //c := color.RGBA{64, 64, 255, 255} //vc.Add(glh.ColorVertex{c, glh.Vertex{, y}}) continue } else if rec.Type == MEMA_FUNC_EXIT { *x = 2 + float32(stack_depth)/80. *y = float32(pos) //int64(len(*vc))) *r, *g, *b = 255, 64, 64 vertices = append(vertices, *x, *y) colours = append(colours, *r, *g, *b) //vc.Add(vertex, colour) //y := float32(int64(len(*vc))) //c := color.RGBA{255, 64, 64, 255} //vc.Add(glh.ColorVertex{c, glh.Vertex{2 + float32(stack_depth)/80., y}}) stack_depth-- continue } else { log.Panic("Unexpected record type: ", rec.Type) } a := rec.MemAccess() page := a.Addr / *PAGE_SIZE if _, present := block.quiet_pages[page]; present { continue } *x = float32((a.Addr - block.n_inactive_to_left[page]*(*PAGE_SIZE))) / float32(width) *x = (*x - 0.5) * 4 if *x > 4 || *x < -4 { log.Panic("x has unexpected value: ", x) } *y = float32(pos) //len(*vc)) *r, *g, *b = uint8(a.IsWrite)*255, uint8(1-a.IsWrite)*255, 0 vertices = append(vertices, *x, *y) colours = append(colours, *r, *g, *b) //vc.Add(vertex, colour) //c := color.RGBA{uint8(a.IsWrite) * 255, uint8(1-a.IsWrite) * 255, 0, 255} //vc.Add(glh.ColorVertex{c, glh.Vertex{x, y}}) /* TODO: Reintroduce 'recently hit memory locations' if pos > (start + N) - N / 20 { vc.Add(ColorVertex{c, Vertex{x, 2 + 0.1}}) vc.Add(ColorVertex{c, Vertex{x, 2}}) } */ } vc.Add(vertices, colours) // Don't need the record data anymore block.records = Records{} runtime.GC() return vc }
// Init creates gl related resources: textures, fonts, buffers, etc. func (d *LEM1802Display) Init() (err error) { for _, dev := range d.cpu.Devices() { if dev, ok := dev.(*lem1802.Device); ok { d.dev = dev break } } d.createAtlas( (d.width-d.borderWidth*2)/lem1802.CellsPerRow, (d.height-d.borderHeight*2)/lem1802.CellsPerCol, ) var pos [4 * 2]int16 var clr [4 * 3]byte var tex [4 * 2]float32 // Untextured quads d.quads_ut = glh.NewMeshBuffer( glh.RenderBuffered, glh.NewPositionAttr(2, gl.SHORT, gl.STATIC_DRAW), glh.NewColorAttr(3, gl.UNSIGNED_BYTE, gl.DYNAMIC_DRAW), ) // Textured quads d.quads_t = glh.NewMeshBuffer( glh.RenderBuffered, glh.NewPositionAttr(2, gl.SHORT, gl.STATIC_DRAW), glh.NewColorAttr(3, gl.UNSIGNED_BYTE, gl.DYNAMIC_DRAW), glh.NewTexCoordAttr(2, gl.FLOAT, gl.DYNAMIC_DRAW), ) // Screen background/border. pos[2] = int16(d.width) pos[4] = int16(d.width) pos[5] = int16(d.height) pos[7] = int16(d.height) d.quads_ut.Add(pos[:], clr[:]) gw := int16((d.width - d.borderWidth*2) / lem1802.CellsPerRow) gh := int16((d.height - d.borderHeight*2) / lem1802.CellsPerCol) // Add individual glyphs. // // These consist of two equal-sized quads stacked on top of // each other. The bottom one is an opaque quad with the glyph's // background color. The top one has the glyph texture with // its foreground color. for y := int16(0); y < lem1802.CellsPerCol; y++ { gy := y*gh + int16(d.borderHeight) for x := int16(0); x < lem1802.CellsPerRow; x++ { gx := x*gw + int16(d.borderWidth) pos[0] = gx pos[1] = gy pos[2] = gx + gw pos[3] = gy pos[4] = gx + gw pos[5] = gy + gh pos[6] = gx pos[7] = gy + gh // Glyph background. d.quads_ut.Add(pos[:], clr[:]) // Glyph. d.quads_t.Add(pos[:], clr[:], tex[:]) } } return }
func main() { var err error if err = glfw.Init(); err != nil { fmt.Fprintf(os.Stderr, "[e] %v\n", err) return } defer glfw.Terminate() w, h := 1980, 1080 // w, h := 1280, 768 if err = glfw.OpenWindow(w, h, 8, 8, 8, 16, 0, 32, glfw.Fullscreen); err != nil { fmt.Fprintf(os.Stderr, "[e] %v\n", err) return } defer glfw.CloseWindow() glfw.SetSwapInterval(1) glfw.SetWindowTitle("Debris") quadric = glu.NewQuadric() gl.Enable(gl.CULL_FACE) gl.Enable(gl.DEPTH_TEST) gl.DepthFunc(gl.LEQUAL) gl.Enable(gl.NORMALIZE) gl.Enable(gl.BLEND) gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) gl.ShadeModel(gl.SMOOTH) gl.Enable(gl.LIGHTING) var ( ambient = []float32{0.1, 0.3, 0.6, 1} diffuse = []float32{1, 1, 0.5, 1} specular = []float32{0.4, 0.4, 0.4, 1} light_position = []float32{1, 0, 0, 0} // mat_specular []float32 = []float32{1, 1, 0.5, 1} mat_specular = []float32{1, 1, 0.75, 1} mat_shininess = float32(120) // light_position []float32 = []float32{0.0, 0.0, 1.0, 0.0} ) const ( fov = 1.1 // degrees znear = 145 zfar = 155 camera_z_offset = -150 camera_x_rotation = 0 // degrees // camera_x_rotation = 20 // degrees starfield_fov = 45 faces = 1000 earth_radius = 1 ) gl.Lightfv(gl.LIGHT1, gl.AMBIENT, ambient) gl.Lightfv(gl.LIGHT1, gl.DIFFUSE, diffuse) gl.Lightfv(gl.LIGHT1, gl.SPECULAR, specular) gl.Lightfv(gl.LIGHT1, gl.POSITION, light_position) gl.Enable(gl.LIGHT1) mat_emission := []float32{0, 0, 0.1, 1} gl.Materialfv(gl.FRONT_AND_BACK, gl.EMISSION, mat_emission) gl.Materialfv(gl.FRONT_AND_BACK, gl.SPECULAR, mat_specular) gl.Materialf(gl.FRONT_AND_BACK, gl.SHININESS, mat_shininess) gl.ClearColor(0.02, 0.02, 0.02, 1) gl.ClearDepth(1) gl.ClearStencil(0) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) b := createBuffer() planetoids := []*Planetoid{} for i := 0; i < 1000; i++ { p := &Planetoid{ apogee: 1.2 + rand.Float64()*0.7, perigee: 1.5, // inclination: 45, inclination: rand.Float64()*20 - 10, // inclination: 0, phase0: rand.Float64() * 360, rising_node: rand.Float64() * 10, phase: 0, // radius: rand.Float32()*0.05 + 0.01, //float32(r), radius: rand.Float32()*0.0125 + 0.005, //float32(r), // quadric: glu.NewQuadric(), circle: b, } planetoids = append(planetoids, p) } // Initial projection matrix: var aspect float64 glfw.SetWindowSizeCallback(func(w, h int) { gl.Viewport(0, 0, w, h) gl.MatrixMode(gl.PROJECTION) gl.LoadIdentity() aspect = float64(w) / float64(h) glu.Perspective(fov, aspect, znear, zfar) }) d := float64(0) wireframe := false atmosphere := false polar := false rotating := false front := false earth := true cone := true shadowing := true tilt := false running := true glfw.SetKeyCallback(func(key, state int) { if state != glfw.KeyPress { // Don't act on key coming up return } switch key { case 'A': atmosphere = !atmosphere case 'C': cone = !cone case 'E': earth = !earth case 'R': rotating = !rotating case 'F': front = !front if front { gl.FrontFace(gl.CW) } else { gl.FrontFace(gl.CCW) } case 'S': shadowing = !shadowing case 'T': tilt = !tilt case 'W': wireframe = !wireframe method := gl.GLenum(gl.FILL) if wireframe { method = gl.LINE } gl.PolygonMode(gl.FRONT_AND_BACK, method) case glfw.KeyF2: println("Screenshot captured") // glh.CaptureToPng("screenshot.png") w, h := glh.GetViewportWH() im := image.NewRGBA(image.Rect(0, 0, w, h)) glh.ClearAlpha(1) gl.Flush() glh.CaptureRGBA(im) go func() { fd, err := os.Create("screenshot.png") if err != nil { panic("Unable to open file") } defer fd.Close() png.Encode(fd, im) }() case 'Q', glfw.KeyEsc: running = !running case glfw.KeySpace: polar = !polar } }) _ = rand.Float64 stars := glh.NewMeshBuffer( glh.RenderArrays, glh.NewPositionAttr(3, gl.DOUBLE, gl.STATIC_DRAW), glh.NewColorAttr(3, gl.DOUBLE, gl.STATIC_DRAW)) const Nstars = 50000 points := make([]float64, 3*Nstars) colors := make([]float64, 3*Nstars) for i := 0; i < Nstars; i++ { const R = 1 phi := rand.Float64() * 2 * math.Pi z := R * (2*rand.Float64() - 1) theta := math.Asin(z / R) points[i*3+0] = R * math.Cos(theta) * math.Cos(phi) points[i*3+1] = R * math.Cos(theta) * math.Sin(phi) points[i*3+2] = z const r = 0.8 v := rand.Float64()*r + (1 - r) colors[i*3+0] = v colors[i*3+1] = v colors[i*3+2] = v } stars.Add(points, colors) render_stars := func() { glh.With(glh.Attrib{gl.DEPTH_BUFFER_BIT | gl.ENABLE_BIT}, func() { gl.Disable(gl.LIGHTING) gl.PointSize(1) gl.Color4f(1, 1, 1, 1) gl.Disable(gl.DEPTH_TEST) gl.DepthMask(false) stars.Render(gl.POINTS) }) } render_scene := func() { // Update light position (sensitive to current modelview matrix) gl.Lightfv(gl.LIGHT1, gl.POSITION, light_position) gl.Lightfv(gl.LIGHT2, gl.POSITION, light_position) if earth { Sphere(earth_radius, faces) } unlit_points := glh.Compound(glh.Disable(gl.LIGHTING), glh.Primitive{gl.POINTS}) glh.With(unlit_points, func() { gl.Vertex3d(1, 0, 0) }) for _, p := range planetoids { const dt = 0.1 // TODO: Frame update p.Render(dt) } glh.With(glh.Disable(gl.LIGHTING), func() { // Atmosphere gl.Color4f(0.25, 0.25, 1, 0.1) if atmosphere && earth { Sphere(earth_radius*1.025, 100) } gl.PointSize(10) glh.With(glh.Primitive{gl.POINTS}, func() { gl.Color4f(1.75, 0.75, 0.75, 1) gl.Vertex3d(-1.04, 0, 0) }) }) } render_shadow_volume := func() { glh.With(glh.Attrib{ gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.ENABLE_BIT | gl.POLYGON_BIT | gl.STENCIL_BUFFER_BIT, }, func() { gl.Disable(gl.LIGHTING) if shadowing { // gl.Disable(gl.DEPTH_TEST) gl.DepthMask(false) gl.DepthFunc(gl.LEQUAL) gl.Enable(gl.STENCIL_TEST) gl.ColorMask(false, false, false, false) gl.StencilFunc(gl.ALWAYS, 1, 0xffffffff) } shadow_volume := func() { const sv_length = 2 const sv_granularity = 100 const sv_radius = earth_radius * 1.001 // Shadow cone glh.With(glh.Matrix{gl.MODELVIEW}, func() { gl.Rotatef(90, 1, 0, 0) gl.Rotatef(90, 0, -1, 0) gl.Color4f(0.5, 0.5, 0.5, 1) glu.Cylinder(quadric, sv_radius, sv_radius*1.05, sv_length, sv_granularity, 1) glu.Disk(quadric, 0, sv_radius, sv_granularity, 1) glh.With(glh.Matrix{gl.MODELVIEW}, func() { gl.Translated(0, 0, sv_length) glu.Disk(quadric, 0, sv_radius*1.05, sv_granularity, 1) }) }) for _, p := range planetoids { p.RenderShadowVolume() } } if cone { gl.FrontFace(gl.CCW) gl.StencilOp(gl.KEEP, gl.KEEP, gl.INCR) shadow_volume() gl.FrontFace(gl.CW) gl.StencilOp(gl.KEEP, gl.KEEP, gl.DECR) shadow_volume() } if shadowing { gl.StencilFunc(gl.NOTEQUAL, 0, 0xffffffff) gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP) gl.ColorMask(true, true, true, true) // gl.Disable(gl.STENCIL_TEST) gl.Disable(gl.DEPTH_TEST) gl.FrontFace(gl.CCW) // gl.Color4f(1, 0, 0, 0.75) gl.Color4f(0, 0, 0, 0.75) // gl.Color4f(1, 1, 1, 0.75) gl.LoadIdentity() gl.Translated(0, 0, camera_z_offset) // TODO: Figure out why this doesn't draw over the whole screen glh.With(glh.Disable(gl.LIGHTING), func() { glh.DrawQuadd(-10, -10, 20, 20) }) // gl.FrontFace(gl.CW) // gl.Enable(gl.LIGHTING) // gl.Disable(gl.LIGHT1) // render_scene() // gl.Enable(gl.LIGHT1) } }) } _ = render_stars for running { running = glfw.WindowParam(glfw.Opened) == 1 glfw.SwapBuffers() gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT) rotation := func() { if tilt { gl.Rotated(20, 1, 0, 0) } if polar { gl.Rotated(90, 1, 0, 0) } gl.Rotated(d, 0, -1, 0) } // Star field glh.With(glh.Matrix{gl.PROJECTION}, func() { gl.LoadIdentity() glu.Perspective(starfield_fov, aspect, 0, 1) glh.With(glh.Matrix{gl.MODELVIEW}, func() { gl.LoadIdentity() rotation() render_stars() }) }) gl.MatrixMode(gl.MODELVIEW) gl.LoadIdentity() gl.Translated(0, 0, camera_z_offset) rotation() if rotating { d += 0.2 } _ = render_scene render_scene() _ = render_shadow_volume render_shadow_volume() } }