Shiny language of the day

Thread Starter

nsaspook

Joined Aug 27, 2009
16,369
https://www.theregister.com/2025/02/05/mixing_rust_and_c_linux/
Mixing Rust and C in Linux likened to cancer by kernel maintainer
If you want to make Linux impossible to maintain due to a cross-language codebase, do that in your driver so that you have to do it instead of spreading this cancer to core subsystems. (where this cancer explicitly is a cross-language codebase and not Rust itself, just to escape the flameware brigade).
...
Every additional bit that another language creeps in drastically reduces the maintainability of the kernel as an integrated project. The only reason Linux managed to survive so long is by not having internal boundaries, and adding another language completely breaks this. You might not like my answer, but I will do everything I can do to stop this. This is NOT because I hate Rust. While not my favorite language it's definitely one of the best new ones and I encourage people to use it for new projects where it fits. I do not want it anywhere near a huge C code base that I need to maintain.
If you want a Rust Linux kernel, then fork the source and rewrite it in Rust.

The problem, they can't, as Rust is currently too immature a language, with limited availability on platforms as compared to C. C is the only HLL on a huge number of platforms running and maintaining version of Linux.
Does Rust have an internationally recognised Standard definition?

I don't see that changing in the nearterm because at its root, the Linux kernel is about making hardware useful for applications by exposing userland API's, not software applications for users. The internal kernel APIs are always in flux because they need to adapt to new hardware systems that can completely fracture older ways of doing things. What made and still makes Linux a success today is the ability to quickly turn new hardware into useful systems while still being able to support old hardware. The Rust theory of software memory safety for hardware is IMO far too idealistic as a solution for the problems of systems programming on hardware.

The true hacks are at the binaries and guess what? Rust uses the same ASM chain as all the other languages.
 
Last edited by a moderator:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,369
https://hackaday.com/2025/02/09/c-is-45-years-old-stroustrup-says-you-still-dont-get-it/
C++ Is 45 Years Old. [Stroustrup] Says You Still Don’t Get It!
We don’t disagree, but C++ is much like its progenitor, C, in that it doesn’t really force you to color inside the lines. We like that, though. But it does mean that people will go off and do things the way they want to do it, for any of a number of good and bad reasons.
https://cacm.acm.org/blogcacm/21st-century-c/

I don't use C++ for controller applications because it's unnecessary complex but safety is in the mind of the programming language user. Trying to straitjacket them with X language will just limit the use of X language because people will avoid the jobs that use it.

We can train 19YO kids to be safe with machine guns and explosives but somehow, it's impossible to train programmers to be safe (don't be stupid) with C or C++ ?
 
Last edited by a moderator:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,369


You see this with kids too.
They were teaching kids how to hold a pencil and color in the dots. Not how to write a simple expression of human though, in any language, using the rules of human expression of ideas.

"Teaching AI to kids" will likely be the next Big Shiny Thing to waste vast sums of money and time on.
 
Last edited:

Futurist

Joined Apr 8, 2025
847
https://hackaday.com/2025/02/09/c-is-45-years-old-stroustrup-says-you-still-dont-get-it/
C++ Is 45 Years Old. [Stroustrup] Says You Still Don’t Get It!


https://cacm.acm.org/blogcacm/21st-century-c/

I don't use C++ for controller applications because it's unnecessary complex but safety is in the mind of the programming language user. Trying to straitjacket them with X language will just limit the use of X language because people will avoid the jobs that use it.

We can train 19YO kids to be safe with machine guns and explosives but somehow, it's impossible to train programmers to be safe (don't be stupid) with C or C++ ?
There are metrics and studies that prove that code quality and bug rates are partly a function of language, not simply programmer skill level. Different problem domains historically tend to be dominated by particular languages, In the 1970s many huge systems were written in COBOL, a mind numbingly unexciting language but those systems worked well and nobody ever noticed them have problems.

Banks, insurance firms, accounting and the like (even NASA), were all heavily reliant on boring old COBOL, but they worked well and lasted decades.

Writing a 24/7 flight booking system for an airline would have been more painful using say C as opposed to COBOL.

https://www.popularmechanics.com/space/a17991/voyager-1-voyager-2-retiring-engineer/
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,369
I've got a personal library filled with expensive books on the Shiny language of the Day. Learning at least the basics of them was a good programming experience and languages like Modula-2 taught me structured programming, instead of unstructured coding. It changed how I think about programming tasks as they form mental images of what's needed. Once I know what's needed in general, then the language and hardware choices begin.

1746277229035.png
1746277745430.png
 
Last edited:

Futurist

Joined Apr 8, 2025
847
I respect anyone who values books like this. Many computer books today are (as you imply) driven by fashion, fads.

Every year I encounter new fads and everyone wants to jump on the bandwagon, I have books bought in past 15 or 20 years that are technically useless (DOS, Visual J++, Open GL, remember all those "open" fads?).

Having said that, my professional work covers designing web accessed software (look up Learning Tool Interoperability) and I evaluated a technology Blazor, which is a breakthrough in terms of enabling single language app development for web.

Blazor makes Javascript more or less redundant yet I became intrigued by Javascript so intend to learn it just for fun.
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,369
Open GL is not a fad, outside of the windows world. A large part of the hardware in a typical NVIDIA GPU is dedicated to it. It simply moved to a hardware API instead of a software API in most of the modern rendering methods. Graphic transformations are generic in math and in silicon.
https://developer.nvidia.com/opengl

Originally developed by Silicon Graphics in the early '90s, OpenGL® has become the most widely-used open graphics standard in the world. NVIDIA supports OpenGL and a complete set of OpenGL extensions, designed to give you maximum performance on our GPUs. NVIDIA continues to support OpenGL as well through technical papers and our large set of examples on our NVIDIA Graphics SDK.

1746283953485.png
1746283984055.png

Java (Javascript is OK too with with good libraries) 3D display from data over CANBUS from one of my controller (C) boards with an IMU chip (https://www.ceva-ip.com/product/bno-9-axis-imu/), that's connected to another on of my controller (C) boards with a Ethernet module for networking.
1746284248806.png


Java:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*
* http://www.java2s.com/example/java-api/javax/media/j3d/orientedshape3d/rotate_about_point-0.html
* send an ascii string of CSV data values via a tcp port
*/
package bnocube;

import java.awt.*;
import java.util.Scanner;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.swing.JFrame;
import javax.vecmath.Quat4d;
import javax.vecmath.Vector3d;
import javax.vecmath.Point3f;
//import javax.vecmath.AxisAngle4f;
//import com.fazecast.jSerialComm.SerialPort;
import com.sun.j3d.utils.geometry.ColorCube;
import javax.media.j3d.Text3D;
import com.sun.j3d.utils.universe.SimpleUniverse;
//import java.util.Properties;
import java.net.Socket;
//import java.net.InetAddress;
import javax.media.j3d.*;

/**
*
* @author root
*/
public class Bnocube {

    public static void main(String[] args) {

        String ttl_eth_host = "NA";
        JFrame frame = new JFrame("Sensor Fusion Visual Test Program");
        Canvas3D canvas = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
        SimpleUniverse universe = new SimpleUniverse(canvas);
        BranchGroup group = new BranchGroup();
        ColorCube cube = new ColorCube(0.3);
        Font myFont = new Font("TimesRoman", Font.CENTER_BASELINE, 1);
        Font3D myFont3D = new Font3D(myFont, new FontExtrusion());
        Point3f textPt = new Point3f(-1.8f, 0.4f, -1.0f); // place the text near the cube
        Text3D myText3D = new Text3D(myFont3D, "BNO086", textPt);
        Shape3D myShape3D = new Shape3D(myText3D, new Appearance());

        TransformGroup transformGroup = new TransformGroup();
        transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        transformGroup.addChild(myShape3D);
        transformGroup.addChild(cube);

        universe.getViewingPlatform().setNominalViewingTransform();
        group.addChild(transformGroup);
        universe.addBranchGraph(group);

        frame.add(canvas);
        frame.setSize(800, 600);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

        ttl_eth_host = "10.1.1.238";

        try {
            Socket socket = new Socket(ttl_eth_host, 20108);
            // send IP address query to remote module
            socket.getOutputStream().write("L\r".getBytes("US-ASCII")); // or UTF-8 or any other applicable encoding...

            // grab data from the IMU host tcp server
            Scanner s = new Scanner(socket.getInputStream());
            System.err.println("Scanner running.");

            while (s.hasNextLine()) {
                try {
                    String line = s.nextLine();
                    String[] token = line.split(",");

                    if (token[0].equals("  1")) {
                        System.out.println(String.format("dtype = %3s  device = %s :%s:%s:%s:%s:%s:%s:%s:", token[0], token[1], token[2], token[3], token[4], token[5], token[6], token[7], token[8]));
                        double x = Double.parseDouble(token[2]);
                        double y = Double.parseDouble(token[3]);
                        double z = Double.parseDouble(token[4]);
                        double w = Double.parseDouble(token[5]);
                        double ax = Double.parseDouble(token[6]);
                        double ay = Double.parseDouble(token[7]);
                        double az = Double.parseDouble(token[8]);
                    
                        // multiply x/y/z by -1 to swap frames of reference
                        Quat4d quaternion = new Quat4d(w, -y, -z, -x); // cube 3D position
                        // -(√2)/2 = -0.70710678118f
                        Quat4d a = new Quat4d(1.0f, 0.0f, 0.0f, 0.0f); // set cube position correction rotation matrix
                        a.normalize(); // make sure we have a proper rotation quaterion
                        quaternion.mul(a); // multiply IMU quaternion with rotation quaternion to flip cube and text
                        Vector3d vector = new Vector3d((ax * 0.02), (ay * 0.02), (az * 0.02)); // acceleration vector for cube vibration
                        transformGroup.setTransform(new Transform3D(quaternion, vector, 0.5)); // place it on the screen with motions from IMU

                        // the inverse cosine of w gives you the pitch *if* you normalize the quaternion with x and z being zero
                        double pitch = Math.acos(w / Math.sqrt(w * w + y * y)) * 2.0 - (Math.PI / 2.0);

                        System.out.println(String.format("w = %+2.3f     x = %+2.3f     y = %+2.3f     z = %+2.3f     pitch = %+1.3f", w, x, y, z, pitch));
                    }
                } catch (Exception e) {
                    System.err.println("Scan Line error.");
                }
            }
            socket.close();
            s.close();
            System.err.println("Lost communication with the IMU socket. Exiting.");
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Unable to open socket to ttl_eth_host. Exiting.");
            System.exit(1);
        }
        System.err.println("Exiting.");
        System.exit(1);
    }
}
BNO086 C driver:
C:
#include "bno086.h"
#include "quat.h"

// sizes of various sensor data packet elements
#define SIZEOF_BASE_TIMESTAMP 5
#define SIZEOF_TIMESTAMP_REBASE 5
#define SIZEOF_ACCELEROMETER 10
#define SIZEOF_LINEAR_ACCELERATION 10
#define SIZEOF_GYROSCOPE_CALIBRATED 10
#define SIZEOF_MAGNETIC_FIELD_CALIBRATED 10
#define SIZEOF_MAGNETIC_FIELD_UNCALIBRATED 16
#define SIZEOF_ROTATION_VECTOR 14
#define SIZEOF_GAME_ROTATION_VECTOR 12
#define SIZEOF_GEOMAGNETIC_ROTATION_VECTOR 14
#define SIZEOF_TAP_DETECTOR 5
#define SIZEOF_STABILITY_REPORT 6
#define SIZEOF_STEP_DETECTOR 8
#define SIZEOF_STEP_COUNTER 12
#define SIZEOF_SIGNIFICANT_MOTION 6
#define SIZEOF_SHAKE_DETECTOR 6
#define SIZEOF_CIRCLE_DETECTOR 6
#define SIZEOF_AMBIENT_DETECTOR 8
#define SIZEOF_PRESSURE_DETECTOR 8
#define SIZEOF_HUMIDITY_DETECTOR 6
#define SIZEOF_PROXIMITY_DETECTOR 6
#define SIZEOF_TEMPERATURE_DETECTOR 6

static const char *build_date = __DATE__, *build_time = __TIME__;
const char *bno_Status = "ULMH";

/*
* IMU sensor data structure
*/
_BnoData_t bno;

uint8_t * txShtpHeader = imu0.tbuf;
uint8_t * rxShtpHeader = imu0.rbuf;

uint8_t * txShtpData = imu0.tbuf + SHTP_HEADER_SIZE;
uint8_t * rxShtpData = imu0.rbuf + SHTP_HEADER_SIZE;

uint16_t rxPacketLength;
uint8_t dummy_header[512] = {0};

/// Current sequence number for each channel, incremented after transmission.
uint8_t sequenceNumber[6];

/// Commands have a seqNum as well. These are inside command packet, the header uses its own seqNum per channel
uint8_t commandSequenceNumber;

#define STATUS_ARRAY_LEN MAX_SENSOR_REPORTID + 1

/// stores status of each sensor, indexed by report ID
uint8_t reportStatus[STATUS_ARRAY_LEN];

/// stores whether a sensor has been updated since the last call to hasNewData()
bool reportHasBeenUpdated[STATUS_ARRAY_LEN];

static bool first = true;

bool bno086_updateData(void)
{

    if (!bno086_receive_packet(&imu0)) {
        // comms error
        return false;
    }

    processPacket();

    // packets were received, so data may have changed
    return true;
}

/*
*
*/
void softreset(void)
{
    txShtpData[0] = 1;

    sendPacket(CHANNEL_EXECUTABLE, 1, &imu0);
}

void bno086_flush(void)
{
    txShtpData[0] = 0xf0;
    txShtpData[2] = 0x0b;

    sendPacket(CHANNEL_CONTROL, 2, &imu0);
}

/*
* Sends the packet to enable reports
*/
void enableReport(enum Report report, uint16_t timeBetweenReports)
{
    // check time is valid
    float periodSeconds = (float) (timeBetweenReports / 1000.0);

    snprintf(imu_buffer, max_buf, "BNO08X time %.06f s", periodSeconds);
    dprintf("%s\r\n", imu_buffer);
    setFeatureCommand((uint8_t) report, timeBetweenReports, 0);
}

void setFeatureCommand(uint8_t reportID, uint16_t timeBetweenReports, uint32_t specificConfig)
{
    uint32_t microsBetweenReports = (uint32_t) (timeBetweenReports * 1000);
    const uint32_t batchMicros = 0;

    txShtpData[0] = SHTP_REPORT_SET_FEATURE_COMMAND; //Set feature command. Reference page 55
    txShtpData[1] = reportID; //Feature Report ID. 0x01 = Accelerometer, 0x05 = Rotation vector
    txShtpData[2] = 0; //Feature flags
    txShtpData[3] = 0; //Change sensitivity (LSB)
    txShtpData[4] = 0; //Change sensitivity (MSB)
    txShtpData[5] = (microsBetweenReports >> 0) & 0xFF; //Report interval (LSB) in microseconds. 0x7A120 = 500ms
    txShtpData[6] = (microsBetweenReports >> 8) & 0xFF; //Report interval
    txShtpData[7] = (microsBetweenReports >> 16) & 0xFF; //Report interval
    txShtpData[8] = (microsBetweenReports >> 24) & 0xFF; //Report interval (MSB)
    txShtpData[9] = (batchMicros >> 0) & 0xFF; //Batch Interval (LSB)
    txShtpData[10] = (batchMicros >> 8) & 0xFF; //Batch Interval
    txShtpData[11] = (batchMicros >> 16) & 0xFF; //Batch Interval
    txShtpData[12] = (batchMicros >> 24) & 0xFF; //Batch Interval (MSB)
    txShtpData[13] = (specificConfig >> 0) & 0xFF; //Sensor-specific config (LSB)
    txShtpData[14] = (specificConfig >> 8) & 0xFF; //Sensor-specific config
    txShtpData[15] = (specificConfig >> 16) & 0xFF; //Sensor-specific config
    txShtpData[16] = (specificConfig >> 24) & 0xFF; //Sensor-specific config (MSB)

    //Transmit packet on channel 2, 17 bytes
    sendPacket(CHANNEL_CONTROL, 17, &imu0);
}

void productIdReg(void)
{
    txShtpData[0] = SHTP_REPORT_PRODUCT_ID_REQUEST; //Set feature command. Reference page 55
    txShtpData[1] = 0;

    //Transmit packet on channel 2, 2 bytes
    sendPacket(CHANNEL_CONTROL, 2, &imu0);
}

void clearSendBuffer(void * imup)
{
    imu_cmd_t * imu = imup;

    memset(imu->tbuf, 0, 512);
}

void bno086_set_spimode(void * imup)
{
    imu_cmd_t * imu = imup;
    bool wait = true;

    /*
     * power boot/reset
     */
    IMU_CS_Set(); // disable IMU
    WaitMs(800);
    TP3_Set(); // wake high for spi mode
    DIS_RESET_Clear();
    WaitMs(5);
    DIS_RESET_Set();
    WaitMs(10);

    dprintf("\r\nbno086 setup start\r\n");
    // set SPI MODE
    set_imu_bits(); // set 8 or 32-bit SPI transfers
    LED_GREEN_Off();
    LED_RED_Off();
    softreset();
    if (imu) {
        if (first) { // power up restart device detection mode
            imu->update = false;
        }
        /*
         * trigger ISR on IMU data update interrupts
         */
        init_imu_int_bno(imu);
        snprintf(cmd_buffer, max_buf, "init_imu_int_bno");
        dprintf("%s\r\n", cmd_buffer);

        if (first) { // lets see if the device is alive and talking
            StartTimer(TMR_BNO_INT, BNO_INIT_DELAY_INT);
            while (wait) {
                if (TimerDone(TMR_BNO_INT)) { // timeout waiting for device
                    wait = false; // exit wait loop to fail detection
                    LED_RED_On();
                    snprintf(imu_buffer, max_buf, "** BNO08X detection failed **");
                    dprintf("%s\r\n", imu_buffer);
                    imu->init_good = false;
                    /*
                     * fake IMU interrupt for testing
                     */
#ifdef FAKE_IMU
                    imu->update = true;
#endif
                }
                if (imu->update) { // ISR set detection flag
                    wait = false;
                    snprintf(imu_buffer, max_buf, "BNO08X interrupt detected");
                    dprintf("%s\r\n", imu_buffer);
                }
                LED_RED_Off();
                LED_GREEN_On();
            }
        }
        /*
         * eat any IMU boot messages
         */
        dprintf("\r\nEmpty IMU message buffer\r\n");
        wait = false;
        StartTimer(TMR_BNO_INT, BNO_INIT_DELAY_INT);
        while (wait && (!TimerDone(TMR_BNO_INT) || imu->update)) {
            if (imu->update) {
                StartTimer(TMR_BNO_INT, BNO_INIT_DELAY_INT);
                imu->update = false;
                clearSendBuffer(imu);
                IMU_CS_Clear();
                SPI2_WriteRead(imu->tbuf, 5, imu->rbuf, 5);
                IMU_CS_Set();
                dprintf("%x %x %x %x %x %c\r\n", imu0.rbuf[0], imu0.rbuf[1], imu0.rbuf[2], imu0.rbuf[3], imu0.rbuf[4], isprint(imu0.rbuf[4]) ? imu0.rbuf[4] : ' ');
            }
        }
        dprintf("\r\nbno086 setup done\r\n");
        first = false;
    }
}

void get_id_dummy(void)
{
    clearSendBuffer(&imu0);
    TP3_Clear(); // send a WAKE signal to the IMU
    delay_us(500);
    TP3_Set();
    IMU_CS_Clear();
    SPI2_WriteRead(imu0.tbuf, 512, imu0.rbuf, 512);
    IMU_CS_Set();
    clearSendBuffer(&imu0);
    TP3_Clear(); // send a WAKE signal to the IMU
    delay_us(500);
    TP3_Set();
    IMU_CS_Clear();
    SPI2_WriteRead(imu0.tbuf, 512, imu0.rbuf, 512);
    IMU_CS_Set();

    txShtpData[0] = SHTP_REPORT_PRODUCT_ID_REQUEST; //Request the product ID and reset info
    txShtpData[1] = 0; //Reserved

    sendPacket(CHANNEL_CONTROL, 2, &imu0);
    waitForPacket(CHANNEL_CONTROL, SHTP_REPORT_PRODUCT_ID_RESPONSE, &imu0);
    snprintf(cmd_buffer, max_buf, "BNO08X waitForPacket");
    dprintf("%s\r\n", cmd_buffer);

    if (rxShtpData[0] == SHTP_REPORT_PRODUCT_ID_RESPONSE) {
        bno.majorSoftwareVersion = rxShtpData[2];
        bno.minorSoftwareVersion = rxShtpData[3];
        bno.patchSoftwareVersion = (rxShtpData[13] << 8) | rxShtpData[12];
        bno.partNumber = (rxShtpData[7] << 24) | (rxShtpData[6] << 16) | (rxShtpData[5] << 8) | rxShtpData[4];
        bno.buildNumber = (rxShtpData[11] << 24) | (rxShtpData[10] << 16) | (rxShtpData[9] << 8) | rxShtpData[8];
        snprintf(response_buffer, max_buf, "\r\nBNO08X reports version %hhu.%hhu.%hu, build %u, part no. %u\r\n",
            bno.majorSoftwareVersion, bno.minorSoftwareVersion, bno.patchSoftwareVersion,
            bno.buildNumber, bno.partNumber);
        dprintf("%s\r\n", response_buffer);
    } else {
        snprintf(imu_buffer, max_buf, "BNO08X bad ID 0x%X", rxShtpData[0]);
        dprintf("%s\r\n", response_buffer);
        LED_RED_On();
        return;
    }
}

bool waitForPacket(int channel, uint8_t reportID, void * imup)
{
    imu_cmd_t * imu = imup;

    StartTimer(TMR_BNO_TO, 5);
    while (!TimerDone(TMR_BNO_TO)) {
        if (TimerDone(TMR_BNO_TO)) {
            if (!bno086_receive_packet(imu)) {
                return false;
            }

            if (channel == rxShtpHeader[2] && reportID == rxShtpData[0]) {
                // found correct packet!
                return true;
            } else {
                // other data packet, send to proper channels
                processPacket();
            }
        }
    }
    return false;
}

bool sendPacket(uint8_t channelNumber, uint8_t dataLength, void * imup)
{
    imu_cmd_t * imu = imup;
    uint16_t totalLength = dataLength + 4; //Add four bytes for the header

    imu->tbuf[0] = totalLength & 0xFF;
    imu->tbuf[1] = totalLength >> 8;
    imu->tbuf[2] = channelNumber;
    imu->tbuf[3] = ++sequenceNumber[channelNumber];

    // wipe out any existing RX packet header so we know if we received a packet
    memset(imu->rbuf, 0, SHTP_HEADER_SIZE);

    // send packet to IMU.
    // This also might receive the first part of another packet, which there is no way to avoid.
    TP3_Clear(); // send a WAKE signal to the IMU
    delay_us(2000);
    TP3_Set();
    IMU_CS_Clear();
    delay_us(2);
    SPI2_WriteRead(imu->tbuf, totalLength, imu->rbuf, totalLength);
    IMU_CS_Set();
    delay_us(100);

    if (imu->rbuf[0] == 0 && imu->rbuf[0] == 0) {
        // no header data so no packet received
        return true;
    } else {
        // received first part of data packet while writing
        if (bno086_get_cpacket(totalLength, imu)) {
            // received data packet, send to proper channels
            if (!first) {
                processPacket();
            }
            return true;
        }

        // receive failed
        return false;
    }
}

void processPacket(void)
{
    dprintf("processPacket\r\n");
    if (rxShtpHeader[2] == CHANNEL_CONTROL) {
        // currently no command reports are read
    } else if (rxShtpHeader[2] == CHANNEL_EXECUTABLE) {
        // currently no executable reports are read
    } else if (rxShtpHeader[2] == CHANNEL_COMMAND) {

    } else if (rxShtpHeader[2] == CHANNEL_REPORTS || rxShtpHeader[2] == CHANNEL_WAKE_REPORTS) {
        if (rxShtpData[0] == SHTP_REPORT_BASE_TIMESTAMP) {
            // sensor data packet
            parseSensorDataPacket();
        }
    }
}

void parseSensorDataPacket(void)
{
    dprintf("parseSensorDataPacket\r\n");
    size_t currReportOffset = 0;

    currReportOffset += SIZEOF_BASE_TIMESTAMP;

    while (currReportOffset < rxPacketLength) {
#ifdef SDEBUG_psd
#endif
        // lots of sensor reports use 3 16-bit numbers stored in bytes 4 through 9
        // we can save some time by parsing those out here.

        uint16_t data1 = (uint16_t) rxShtpData[currReportOffset + 5] << 8 | rxShtpData[currReportOffset + 4];
        uint16_t data2 = (uint16_t) rxShtpData[currReportOffset + 7] << 8 | rxShtpData[currReportOffset + 6];
        uint16_t data3 = (uint16_t) rxShtpData[currReportOffset + 9] << 8 | rxShtpData[currReportOffset + 8];
        uint8_t reportNum = rxShtpData[currReportOffset];

        if (reportNum != SENSOR_REPORTID_TIMESTAMP_REBASE) {
            // set status from byte 2
            reportStatus[reportNum] = (uint8_t) (rxShtpData[currReportOffset + 2] & 0b11);

            // set updated flag
            reportHasBeenUpdated[reportNum] = true;
        }

        switch (rxShtpData[currReportOffset]) {
        case SENSOR_REPORTID_TIMESTAMP_REBASE:
            dprintf("SENSOR_REPORTID_TIMESTAMP_REBASE\r\n");
            currReportOffset += SIZEOF_TIMESTAMP_REBASE;
            break;
        case SENSOR_REPORTID_ACCELEROMETER:
            dprintf("SENSOR_REPORTID_ACCELEROMETER\r\n");
            bno.totalAcceleration.v[0] = qToFloat(data1, ACCELEROMETER_Q_POINT);
            bno.totalAcceleration.v[1] = qToFloat(data2, ACCELEROMETER_Q_POINT);
            bno.totalAcceleration.v[2] = qToFloat(data3, ACCELEROMETER_Q_POINT);
            bno.statusA = reportStatus[reportNum];

            currReportOffset += SIZEOF_ACCELEROMETER;
            break;
        case SENSOR_REPORTID_LINEAR_ACCELERATION:
            bno.linearAcceleration.v[0] = qToFloat(data1, ACCELEROMETER_Q_POINT);
            bno.linearAcceleration.v[1] = qToFloat(data2, ACCELEROMETER_Q_POINT);
            bno.linearAcceleration.v[2] = qToFloat(data3, ACCELEROMETER_Q_POINT);

            currReportOffset += SIZEOF_LINEAR_ACCELERATION;
            break;
        case SENSOR_REPORTID_GRAVITY:
            bno.gravityAcceleration.v[0] = qToFloat(data1, ACCELEROMETER_Q_POINT);
            bno.gravityAcceleration.v[1] = qToFloat(data2, ACCELEROMETER_Q_POINT);
            bno.gravityAcceleration.v[2] = qToFloat(data3, ACCELEROMETER_Q_POINT);

            currReportOffset += SIZEOF_LINEAR_ACCELERATION;
            break;
        case SENSOR_REPORTID_GYROSCOPE_CALIBRATED:
            bno.gyroRotation.v[0] = qToFloat(data1, GYRO_Q_POINT);
            bno.gyroRotation.v[1] = qToFloat(data2, GYRO_Q_POINT);
            bno.gyroRotation.v[2] = qToFloat(data3, GYRO_Q_POINT);
            bno.statusG = reportStatus[reportNum];

            currReportOffset += SIZEOF_GYROSCOPE_CALIBRATED;
            break;
        case SENSOR_REPORTID_MAGNETIC_FIELD_CALIBRATED:
            bno.magField.v[0] = qToFloat(data1, MAGNETOMETER_Q_POINT);
            bno.magField.v[1] = qToFloat(data2, MAGNETOMETER_Q_POINT);
            bno.magField.v[2] = qToFloat(data3, MAGNETOMETER_Q_POINT);
            bno.statusM = reportStatus[reportNum];

            currReportOffset += SIZEOF_MAGNETIC_FIELD_CALIBRATED;
            break;
        case SENSOR_REPORTID_MAGNETIC_FIELD_UNCALIBRATED:
        {
            bno.magFieldUncalibrated.v[0] = qToFloat(data1, MAGNETOMETER_Q_POINT);
            bno.magFieldUncalibrated.v[1] = qToFloat(data2, MAGNETOMETER_Q_POINT);
            bno.magFieldUncalibrated.v[2] = qToFloat(data3, MAGNETOMETER_Q_POINT);

            uint16_t ironOffsetXQ = (uint16_t) rxShtpData[currReportOffset + 11] << 8 | rxShtpData[currReportOffset + 10];
            uint16_t ironOffsetYQ = (uint16_t) rxShtpData[currReportOffset + 13] << 8 | rxShtpData[currReportOffset + 12];
            uint16_t ironOffsetZQ = (uint16_t) rxShtpData[currReportOffset + 15] << 8 | rxShtpData[currReportOffset + 14];

            bno.hardIronOffset.v[0] = qToFloat(ironOffsetXQ, MAGNETOMETER_Q_POINT);
            bno.hardIronOffset.v[1] = qToFloat(ironOffsetYQ, MAGNETOMETER_Q_POINT);
            bno.hardIronOffset.v[1] = qToFloat(ironOffsetZQ, MAGNETOMETER_Q_POINT);

            currReportOffset += SIZEOF_MAGNETIC_FIELD_UNCALIBRATED;
        }
            break;
        case SENSOR_REPORTID_ROTATION_VECTOR:
        {
            uint16_t realPartQ = (uint16_t) rxShtpData[currReportOffset + 11] << 8 | rxShtpData[currReportOffset + 10];
            uint16_t accuracyQ = (uint16_t) rxShtpData[currReportOffset + 13] << 8 | rxShtpData[currReportOffset + 12];

            dprintf("SENSOR_REPORTID_ROTATION_VECTOR\r\n");
            bno.rotationVector.v[0] = qToFloat(data1, ROTATION_Q_POINT);
            bno.rotationVector.v[1] = qToFloat(data2, ROTATION_Q_POINT);
            bno.rotationVector.v[2] = qToFloat(data3, ROTATION_Q_POINT);
            bno.rotationVector.w = qToFloat(realPartQ, ROTATION_Q_POINT);

            bno.rotationAccuracy = qToFloat(accuracyQ, ROTATION_ACCURACY_Q_POINT);

            currReportOffset += SIZEOF_ROTATION_VECTOR;
        }
            break;
        case SENSOR_REPORTID_GAME_ROTATION_VECTOR:
        {
            uint16_t realPartQ = (uint16_t) rxShtpData[currReportOffset + 11] << 8 | rxShtpData[currReportOffset + 10];

            bno.gameRotationVector.v[0] = qToFloat(data1, ROTATION_Q_POINT);
            bno.gameRotationVector.v[1] = qToFloat(data2, ROTATION_Q_POINT);
            bno.gameRotationVector.v[2] = qToFloat(data3, ROTATION_Q_POINT);
            bno.gameRotationVector.w = qToFloat(realPartQ, ROTATION_Q_POINT);

            currReportOffset += SIZEOF_GAME_ROTATION_VECTOR;
        }
            break;
        case SENSOR_REPORTID_GEOMAGNETIC_ROTATION_VECTOR:
        {
            uint16_t realPartQ = (uint16_t) rxShtpData[currReportOffset + 11] << 8 | rxShtpData[currReportOffset + 10];
            uint16_t accuracyQ = (uint16_t) rxShtpData[currReportOffset + 13] << 8 | rxShtpData[currReportOffset + 12];

            bno.geomagneticRotationVector.v[0] = qToFloat(data1, ROTATION_Q_POINT);
            bno.geomagneticRotationVector.v[1] = qToFloat(data2, ROTATION_Q_POINT);
            bno.geomagneticRotationVector.v[2] = qToFloat(data3, ROTATION_Q_POINT);
            bno.geomagneticRotationVector.w = qToFloat(realPartQ, ROTATION_Q_POINT);

            bno.geomagneticRotationAccuracy = qToFloat(accuracyQ, ROTATION_ACCURACY_Q_POINT);

            currReportOffset += SIZEOF_GEOMAGNETIC_ROTATION_VECTOR;
        }
            break;
        case SENSOR_REPORTID_TAP_DETECTOR:

            // since we got the report, a tap was detected
            bno.tapDetected = true;

            bno.doubleTap = (rxShtpData[currReportOffset + 4] & (1 << 6)) != 0;

            currReportOffset += SIZEOF_TAP_DETECTOR;
            break;
        case SENSOR_REPORTID_STABILITY_CLASSIFIER:
        {
            uint8_t classificationNumber = rxShtpData[currReportOffset + 4];

            if (classificationNumber > 4) {
                classificationNumber = 0;
            }

            bno.stability = (enum Stability) (classificationNumber);
            currReportOffset += SIZEOF_STABILITY_REPORT;
        }
            break;
        case SENSOR_REPORTID_STEP_DETECTOR:
            // the fact that we got the report means that a step was detected
            bno.stepDetected = true;
            currReportOffset += SIZEOF_STEP_DETECTOR;
            break;
        case SENSOR_REPORTID_STEP_COUNTER:
            bno.stepCount = rxShtpData[currReportOffset + 9] << 8 | rxShtpData[currReportOffset + 8];
            currReportOffset += SIZEOF_STEP_COUNTER;
            break;
        case SENSOR_REPORTID_SIGNIFICANT_MOTION:
            // the fact that we got the report means that significant motion was detected
            bno.significantMotionDetected = true;

            currReportOffset += SIZEOF_SIGNIFICANT_MOTION;
            break;
        case SENSOR_REPORTID_SHAKE_DETECTOR:
            bno.shakeDetected = true;

            bno.xAxisShake = (rxShtpData[currReportOffset + 4] & 1) != 0;
            bno.yAxisShake = (rxShtpData[currReportOffset + 4] & (1 << 1)) != 0;
            bno.zAxisShake = (rxShtpData[currReportOffset + 4] & (1 << 2)) != 0;

            currReportOffset += SIZEOF_SHAKE_DETECTOR;
            break;
        case SENSOR_REPORTID_AMBIENT_DETECTOR:
        {
            uint32_t env_data;
            env_data = (uint32_t) rxShtpData[currReportOffset + 7] << 24 | rxShtpData[currReportOffset + 6] << 16 | rxShtpData[currReportOffset + 5] << 8 | rxShtpData[currReportOffset + 4];
            bno.ambient = qToFloat_dword(env_data, 8);
        }
            currReportOffset += SIZEOF_AMBIENT_DETECTOR;
            break;
        case SENSOR_REPORTID_PRESSURE_DETECTOR:
        {
            uint32_t env_data;
            env_data = (uint32_t) rxShtpData[currReportOffset + 7] << 24 | rxShtpData[currReportOffset + 6] << 16 | rxShtpData[currReportOffset + 5] << 8 | rxShtpData[currReportOffset + 4];
            bno.pressure = qToFloat_dword(env_data, 20);
        }
            currReportOffset += SIZEOF_PRESSURE_DETECTOR;
            break;
        case SENSOR_REPORTID_TEMPERATURE_DETECTOR:
        {
            uint16_t env_data;
            env_data = (uint16_t) rxShtpData[currReportOffset + 5] << 8 | rxShtpData[currReportOffset + 4];
            bno.temperature = qToFloat(env_data, 7);
        }
            currReportOffset += SIZEOF_TEMPERATURE_DETECTOR;
            break;
        case SENSOR_REPORTID_PROXIMITY_DETECTOR:
        {
            TP2_Toggle();
            uint16_t env_data;
            env_data = (uint16_t) rxShtpData[currReportOffset + 5] << 8 | rxShtpData[currReportOffset + 4];
            bno.proximity = qToFloat(env_data, 4);
        }
            currReportOffset += SIZEOF_PROXIMITY_DETECTOR;
            break;
        case SENSOR_REPORTID_HUMIDITY_DETECTOR:
        {
            uint16_t env_data;
            env_data = (uint16_t) rxShtpData[currReportOffset + 5] << 8 | rxShtpData[currReportOffset + 4];
            bno.humidity = qToFloat(env_data, 8);
        }
            currReportOffset += SIZEOF_HUMIDITY_DETECTOR;
            break;
        case SENSOR_REPORTID_CIRCLE_DETECTOR:
            bno.circleDetected = true;

            currReportOffset += SIZEOF_CIRCLE_DETECTOR;
            break;
        default:
            return;
            break;
        }
    }
}

void bno086_get_header(void * imup)
{
    imu_cmd_t * imu = imup;

    IMU_CS_Clear();
    SPI2_WriteRead(dummy_header, SHTP_HEADER_SIZE, imu->rbuf, SHTP_HEADER_SIZE);
    IMU_CS_Set();
}

// check for new data and read contents into imu.rbuf

bool bno086_receive_packet(void * imup)
{
    imu_cmd_t * imu = imup;

    bno086_get_header(imu); // first 4 bytes
    delay_us(100);
    if (bno086_get_cpacket(SHTP_HEADER_SIZE, imu)) {
        dprintf("bno086_receive_packet\r\n");
        delay_us(100);
        processPacket();
        return true;
    }
    return false;
}

bool bno086_get_cpacket(size_t read_b, void * imup)
{
    imu_cmd_t * imu = imup;
    uint16_t totalLength;

    dprintf("bno086_get_packet\r\n");
    if (imu->rbuf[0] == 0xff && imu->rbuf[1] == 0xff) { // check for invalid device data
        // not a real failure usually
        snprintf(response_buffer, max_buf, "BNO08X bad, invalid data");
        return false;
    }

    //Calculate the number of data bytes in this packet
    totalLength = (uint16_t) imu->rbuf[0] + (((uint16_t) imu->rbuf[1]) << 8);

    // Clear the MSbit for a continued data packet
    totalLength &= ~(1 << 15);

    if (totalLength == 0) {
        // Packet is empty
        snprintf(response_buffer, max_buf, "BNO08X empty packet");
        dprintf("%s\r\n", response_buffer);
        return(false); //All done
    }

    rxPacketLength = totalLength - SHTP_HEADER_SIZE;

    if (totalLength <= read_b) {
        // the original transaction already read the completed packet!  We're done.
        LED_GREEN_Set();
        return true;
    }

    if (rxPacketLength > SHTP_RX_PACKET_SIZE) {
        snprintf(response_buffer, max_buf, "BNO08X long packet 0x%x > 0x%x", rxPacketLength, SHTP_RX_PACKET_SIZE);
        dprintf("%s\r\n", response_buffer);
        LED_RED_On();
        return false;
    }

    snprintf(response_buffer, max_buf, "BNO08X packet 0x%x 0x%x", rxPacketLength, SHTP_RX_PACKET_SIZE);
    dprintf("%s\r\n", response_buffer);

    IMU_CS_Clear();
    if (read_b == SHTP_HEADER_SIZE) {
        // just read the entire packet into the buffer
        SPI2_WriteRead(dummy_header, totalLength, imu->rbuf, totalLength);
        dprintf("Read entire packet into rbuf\r\n");
    } else {
        // we want to receive a new header, plus the remaining data bytes that haven't been read.
        size_t receiveLength = SHTP_HEADER_SIZE + (totalLength - read_b);

        // read remaining bytes into the data buffer starting at the next byte
        SPI2_WriteRead(dummy_header, receiveLength, imu->rbuf + read_b, receiveLength);

        // erase the new header we just read, leaving only the data as a contiguous block
        memmove(imu->rbuf + read_b, imu->rbuf + read_b + SHTP_HEADER_SIZE, receiveLength - SHTP_HEADER_SIZE);
    }
    IMU_CS_Set();
    LED_GREEN_Set();
    return true;
}

bool bno086_getid(void * imup)
{
    imu_cmd_t * imu = imup;

    if (imu) {
        imu->device = IMU_BNO086;
        imu->angles = false;
        imu->online = true;
        get_id_dummy();
        return true;
    }
    return false;
}

bool bno086_getdata(void * imup)
{
    imu_cmd_t * imu = imup;

    if (imu) {
        bno086_receive_packet(imu);
        return imu->online;
    } else {
        return false;
    }
}

void bno086_version(void)
{
    snprintf(imu_buffer, max_buf, "%s Driver %s %s %s ", BNO086_ALIAS, BNO086_DRIVER, build_date, build_time);
}

//Given a register value and a Q point, convert to float
//See https://en.wikipedia.org/wiki/Q_(number_format)

double qToFloat(int16_t fixedPointValue, uint8_t qPoint)
{
    double qFloat = fixedPointValue;
    qFloat *= pow(2.0f, -qPoint);
    return(qFloat);
}

double qToFloat_dword(uint32_t fixedPointValue, uint16_t qPoint)
{
    double qFloat = fixedPointValue;
    qFloat *= pow(2.0f, -qPoint);
    return(qFloat);
}

//Given a floating point value and a Q point, convert to Q
//See https://en.wikipedia.org/wiki/Q_(number_format)

uint16_t floatToQ(float qFloat, uint8_t qPoint)
{
    uint16_t qVal = (uint16_t) (qFloat * pow(2.0f, qPoint));
    return qVal;
}

uint32_t floatToQ_dword(float qFloat, uint16_t qPoint)
{
    uint32_t qVal = (uint32_t) (qFloat * pow(2.0f, qPoint));
    return qVal;
}

/*
* dummy printf replacement for debug lines
*/
int printf_stub(const char* s, ...)
{
    return 1; // dummy printed char value
}

bool bno086_hasNewData(const uint8_t reportNum)
{
    if (reportNum > STATUS_ARRAY_LEN) {
        return false;
    }

    bool newData = reportHasBeenUpdated[reportNum];
    reportHasBeenUpdated[reportNum] = false; // clear flag
    return newData;
}

void sendTareCommand(uint8_t command, uint8_t axis, uint8_t rotationVectorBasis)
{
    int16_t * reor = (int16_t *) & imu0.tbuf[9];

    for (uint8_t x = 3; x < 12; x++) //Clear this section of the txdata array
        txShtpData[x] = 0;

    txShtpData[3] = command;

    if (command == TARE_NOW) {
        txShtpData[4] = axis; // axis setting
        txShtpData[5] = rotationVectorBasis; // rotation vector
    }

    if (command == TARE_SET_REORIENTATION) {
        reor[0] = floatToQ(0.0f, 14);
        reor[1] = floatToQ(01.0f, 14);
        reor[2] = floatToQ(0.0f, 14);
        reor[3] = floatToQ(0.0f, 14);
    }


    //Using this txshtpData packet, send a command
    sendCommand(COMMAND_TARE);
}

void sendCommand(uint8_t command)
{
    txShtpData[0] = SHTP_REPORT_COMMAND_REQUEST; //Command Request
    txShtpData[1] = commandSequenceNumber++; //Increments automatically each function call
    txShtpData[2] = command; //Command
    //Transmit packet on channel 2, 12 bytes
    sendPacket(CHANNEL_CONTROL, 12, &imu0);
}

bool enableCalibration(bool calibrateAccel, bool calibrateGyro, bool calibrateMag)
{
    // send the Configure ME Calibration command
    clearSendBuffer(&imu0);
    txShtpData[3] = (uint8_t) (calibrateAccel ? 1 : 0);
    txShtpData[4] = (uint8_t) (calibrateGyro ? 1 : 0);
    txShtpData[5] = (uint8_t) (calibrateMag ? 1 : 0);
    txShtpData[6] = 0; // Configure ME Calibration command
    txShtpData[7] = 0; // planar accelerometer calibration always disabled
    sendCommand(COMMAND_ME_CALIBRATE);
    // now, wait for the response
    if (!waitForPacket(CHANNEL_CONTROL, SHTP_REPORT_COMMAND_RESPONSE, &imu0)) {
        snprintf(cmd_buffer, max_buf, "Timeout waiting for calibration response!");
        snprintf(response_buffer, max_buf, " enableCalibration");
        return false;
    }
    if (rxShtpData[2] != COMMAND_ME_CALIBRATE) {
        snprintf(cmd_buffer, max_buf, "Received wrong response to calibration command!");
        snprintf(response_buffer, max_buf, " enableCalibration");
        return false;
    }
    if (rxShtpData[5] != 0) {
        snprintf(cmd_buffer, max_buf, "IMU reports calibrate command failed!");
        snprintf(response_buffer, max_buf, " enableCalibration");
        return false;
    }
    // acknowledge checks out!
    return true;
}

bool saveCalibration(void)
{
    clearSendBuffer(&imu0);

    // no arguments
    sendCommand(COMMAND_SAVE_DCD);
    // now, wait for the response
    if (!waitForPacket(CHANNEL_CONTROL, SHTP_REPORT_COMMAND_RESPONSE, &imu0)) {
        snprintf(cmd_buffer, max_buf, "Timeout waiting for calibration response!");
        snprintf(response_buffer, max_buf, " saveCalibration");
        return false;
    }
    if (rxShtpData[2] != COMMAND_SAVE_DCD) {
        snprintf(cmd_buffer, max_buf, "Received wrong response to calibration command!");
        snprintf(response_buffer, max_buf, " saveCalibration");
        return false;
    }
    if (rxShtpData[5] != 0) {
        snprintf(cmd_buffer, max_buf, "IMU reports calibrate command failed!");
        snprintf(response_buffer, max_buf, " saveCalibration");
        return false;
    }
    // acknowledge checks out!
    return true;
}
Older Shiny things that made a come-back.
The IMU supplies data in Quaternion format, so most of the GL transformations are using them.
https://en.wikipedia.org/wiki/Quaternion

However, quaternions have had a revival since the late 20th century, primarily due to their utility in describing spatial rotations. The representations of rotations by quaternions are more compact and quicker to compute than the representations by matrices. In addition, unlike Euler angles, they are not susceptible to "gimbal lock". For this reason, quaternions are used in computer graphics,[16][17] computer vision, robotics,[18] nuclear magnetic resonance image sampling,[6] control theory, signal processing, attitude control, physics, bioinformatics, molecular dynamics, computer simulations, and orbital mechanics. For example, it is common for the attitude control systems of spacecraft to be commanded in terms of quaternions. Quaternions have received another boost from number theory because of their relationships with the quadratic forms.[19]
 
Last edited:

Futurist

Joined Apr 8, 2025
847
Open GL is not a fad, outside of the windows world. A large part of the hardware in a typical NVIDIA GPU is dedicated to it. It simply moved to a hardware API instead of a software API in most of the modern rendering methods. Graphic transformations are generic in math and in silicon.
https://developer.nvidia.com/opengl

Originally developed by Silicon Graphics in the early '90s, OpenGL® has become the most widely-used open graphics standard in the world. NVIDIA supports OpenGL and a complete set of OpenGL extensions, designed to give you maximum performance on our GPUs. NVIDIA continues to support OpenGL as well through technical papers and our large set of examples on our NVIDIA Graphics SDK.
Yes it does exist and is still used, but many years ago it was the only game in town, if you did serious 3D you'd use Open GL. But those days have faded now, for example the majority of modern games code to the DirectX API, I don't think many people code for Open GL, certainly not like in the early 90s anyway.

I have that 3D graphics book!

Java (Javascript is OK too with with good libraries) 3D display from data over CANBUS from one of my controller (C) boards with an IMU chip (https://www.ceva-ip.com/product/bno-9-axis-imu/), that's connected to another on of my controller (C) boards with a Ethernet module for networking.

Older Shiny things that made a come-back.
The IMU supplies data in Quaternion format, so most of the GL transformations are using them.
https://en.wikipedia.org/wiki/Quaternion

However, quaternions have had a revival since the late 20th century, primarily due to their utility in describing spatial rotations. The representations of rotations by quaternions are more compact and quicker to compute than the representations by matrices. In addition, unlike Euler angles, they are not susceptible to "gimbal lock". For this reason, quaternions are used in computer graphics,[16][17] computer vision, robotics,[18] nuclear magnetic resonance image sampling,[6] control theory, signal processing, attitude control, physics, bioinformatics, molecular dynamics, computer simulations, and orbital mechanics. For example, it is common for the attitude control systems of spacecraft to be commanded in terms of quaternions. Quaternions have received another boost from number theory because of their relationships with the quadratic forms.[19]
I've never used quaternions, I did write a 3D "engine" many years ago and used the book you have and several others too, but the hardware/language was not performant to do ambitious stuff like hidden surface elimination or shadow or any fancy stuff but I did do wire frames with back face elimination and was able to render convex polyhedra and paint their surfaces and arbitrarily rotate them but it was not quite real-time looking.

Back face elimination was easy (and fast) too, just compute the normal to each surface and decide if that normal pointed to or away from the observer, if it points away then don't draw the surface (like don't draw the back of a house if the observer is looking at the front) even simple wire frames look pretty good once BFE is in place.

In essence I had two 3D cartesian coordinate systems one for the observer and the other for the "world" so to speak. It did two things 1) Compute the position of points in the world frame into positions in the observer's frame based on the frames' relative position and orientation and 2) compute the position of the points onto a surface representing the observer's eye, this was a perspective transformation and that's really the guts of the geometry I think - if a person understands these two then they understand 3D IMHO.

Very interesting work but nothing compared to what we can do now.

That's some serious stuff you did there, pretty sophisticated.
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,369
Yes it does exist and is still used, but many years ago it was the only game in town, if you did serious 3D you'd use Open GL. But those days have faded now, for example the majority of modern games code to the DirectX API, I don't think many people code for Open GL, certainly not like in the early 90s anyway.

I have that 3D graphics book!



I've never used quaternions, I did write a 3D "engine" many years ago and used the book you have and several others too, but the hardware/language was not performant to do ambitious stuff like hidden surface elimination or shadow or any fancy stuff but I did do wire frames with back face elimination and was able to render convex polyhedra and paint their surfaces and arbitrarily rotate them but it was not quite real-time looking.

Back face elimination was easy (and fast) too, just compute the normal to each surface and decide if that normal pointed to or away from the observer, if it points away then don't draw the surface (like don't draw the back of a house if the observer is looking at the front) even simple wire frames look pretty good once BFE is in place.

In essence I had two 3D cartesian coordinate systems one for the observer and the other for the "world" so to speak. It did two things 1) Compute the position of points in the world frame into positions in the observer's frame based on the frames' relative position and orientation and 2) compute the position of the points onto a surface representing the observer's eye, this was a perspective transformation and that's really the guts of the geometry I think - if a person understands these two then they understand 3D IMHO.

Very interesting work but nothing compared to what we can do now.

That's some serious stuff you did there, pretty sophisticated.
You're thinking of the Windows World with DirectX API. There's much more to the Universe than MikeRoweSoft games (not a computer gamer, so I have no idea what's being used today on Windows).

That book is about fundamental theory and it's pretty old. The book in the first photo is better for more complex graphics concepts but it's not a modern text either.
https://archive.org/details/computergraphics0000harr_d9i9
Computer graphics : a programming approach

https://www.amazon.com/Computer-Graphics-Programming-Steven-Harrington/dp/0070267537


https://www.allaboutcircuits.com/te...lost-in-deep-space-understanding-quaternions/
Quaternions are what Maxwell used in his original EM theory mathematics. What we normally used today are Vector Calculus versions of the original Quaternion.

To read and understand Maxwell's original writings you need to GROK Quaternions at the engineering level.
https://www.allaboutcircuits.com/technical-articles/maxwells-equations-in-present-form/
https://forum.allaboutcircuits.com/threads/pic32mk-mc-qei-example.150351/post-1560143

 
Last edited:

Futurist

Joined Apr 8, 2025
847
You're thinking of the Windows World with DirectX API. There's much more to the Universe than MikeRoweSoft games (not a computer gamer, so I have no idea what's being used toady on Windows).
Unity is the name of the game for many games on Windows:

 

Futurist

Joined Apr 8, 2025
847
Think my kid is using that on her CS game project. Looks cool and powerful but I've no interest in playing computer games or making them.
Nor me, I'd rather design and OS or something meaty although there are rather a lot of those around in the MCU world these days.
 
Last edited:
Top