#!/bin/sh
#=============================
# 3D - Projects a 3D object to a 2D screen
# Inspired by "eink algorithmic art" thread at http://www.mobileread.com/forums/showthread.php?t=172182
# usage: 3D
# Only tested on Kindle3 (firmware 3.3, Busybox 1.7.2 Almquist Shell)
#------------------------------
# Copyright (c) 2012 PoP under MIT License:
# Copyright (c) 2012 geekmaster under MIT License:
# http://www.opensource.org/licenses/mit-license.php
#------------------------------
# Revision History:
# v4.0  2012-04-05 PoP            ported to Kindle3                  
# v3.0  1986-12-20 PoP            ported to IBM PC
# v2.0  1983-09-14 PoP            ported to C=64
# v1.0  1982-11-11 PoP            ported to Apple][
# v0.0  1981-01-01 A. Pickholtz   see pp 474-505 BYTE magazine: http://malus.exotica.org.uk/~buzz/byte/pdf/BYTE%20Vol%2007-11%201982-11%20Graphics.pdf
#------------------------------
# Global vars:
# B	bank -PI..PI scaled
# CB	cos(bank)
# CH	cos(heading)
# CM	var C of 3d matrix  scaled
# CP	cos(pitch)
# BLOCK vectors of this object scaled
# D	distance from camera scaled
# DF	device frame buffer
# DK	device keyboard
# DM	var D of 3d matrix scaled
# DN	device null
# DODECA vectors of this object scaled
# EM	var E of 3d matrix scaled
# EDGE	absolute value of EDGEi
# EDGEi	OBJ current edge
# EDGE$i OBJ $i'th edge $i i-1..NE
# FP    floating point scaling 10^4
# FH    floating point rounding FP/2
# FM	var F of 3d matrix scaled
# GM	var G of 3d matrix scaled
# H	heading -PI..PI scaled
# HX    middle x screen pos 300
# HY    middle y screen pos 400
# HM	var H of 3d matrix scaled
# IM	var I of 3d matrix scaled
# KQ    bytes per pixel, 2 for Kindle3
# MX	max x pos 0:599
# MY    max y pos 0:799
# NE	OBJ number of edges
# NV	OBJ number of points
# OBJ   current object scaled
# OCTA  vectors of this object
# P	pitch -PI..PI scaled
# P2    2*PI scaled
# PI    PI scaled
# PN    -PI scaled
# SB	sin(bank)
# SH	sin(heading)
# SP	sin(pitch)
# TK	device temporary key
# TETRA vectors of this object scaled
# U	computing 2D projection scaled
# V	computing 2D projection scaled
# VECTX$i OBJ $i'th X coord i=1..NV scaled
# VECTY$i OBJ $i'th Y coord i=1..NV scaled
# VECTZ$i OBJ $i'th Z coord i=1..NV scaled
# VX    virtual x size, 300 for Kindle 3
# XX	computing 2D projection scaled
# YY	computing 2D projection scaled
# ZZ	computing 2D projection scaled
# x1	temp coordinate scaled
# x2	temp coordinate scaled
# X3	computing 2D projection scaled
# y1	temp coordinate scaled
# y2	temp coordinate scaled
# Y3	computing 2D projection scaled
# Z3	computing 2D projection scaled
#-----------------------------

#=============================
# initvar - init global vars
#-----------------------------
initvar() {
  DN=/dev/null DF=/dev/fb0 DK=/dev/input/event0 MX=599 MY=799 HX=300 HY=400
  TK=/tmp/key; echo > $TK; 
  set $(eips -i|grep line_length); VX=$4
  FP=10000 # fixed-point scale factor
  FH=5000 # FP/2 (for rounding)
  PI=31416 # PI (scaled)
  P2=62832 # 2*PI (scaled)
  PN=-31416 # -PI (scaled)
  KQ=2 # K3 pixels per byte
  P=0 B=0 H=0 D=135000
  BLOCK="
	 8
	  52500	 -20000	  32500
	 -52500	 -20000	  32500
	 -52500	 -20000	 -32500
	  52500	 -20000	 -32500
	  52500	  20000	  32500
	 -52500	  20000	  32500
	 -52500	  20000	 -32500
	  52500	  20000	 -32500
	 17
	 -1 2 3 4 1 5 6 7 8 5
	 -2 6 3 7 2
	 -4 8"
  DODECA="
	 20
	      0	 -21408	  56052
	  34644	 -34644	  34644
	  56052	      0	  21408
	  34644	  34644	  34644
	      0	  21408	  56052
	 -34644	  34644	  34644
	 -56052	      0	  21408
	 -34644	 -34644	  34644
	 -21408	 -56052	      0
	  21408	 -56052	      0
	  21408	  56052	      0
	 -21408	  56052	      0
	 -34644	 -34644	 -34644
	      0	 -21408	 -56052
	  34644	 -34644	 -34644
	  56052	      0	 -21408
	  34644	  34644	 -34644
	      0	  21408	 -56052
	 -34644	  34644	 -34644
	 -56052	      0	 -21408
	40
	 -1 2 3 4 5 6 7 8 1 5
	 -14 15 16 17 18 19 20 13 14 18
	 -8 9 10 2
	 -13 9
	 -15 10
	 -4 11 12 6
	 -17 11
	 -19 12
	 -16 3
	 -7 20"
  OCTA="
	 6
	      0	  75000	      0
	      0	      0	 -25000
	 -25000	      0	      0
	      0	      0	  25000
	  25000	      0	      0
	      0	 -75000	      0
	 14
	 -1 2 6 4 1 3 6 5 1
	 -2 3 4 5 2"
  TETRA="
	 4
	 -45000	 -45000	 -45000
	  45000	 -45000	 -45000
	      0	  45000	      0
	      0	  45000	 -45000
	 8
	 -1 2 3 1 4 2 3 4"
}

#===========================
# eupd - eink update display
#---------------------------
eupd() {
  echo 1 >/proc/eink_fb/update_display # (for k3 and earlier)
#  eips '' # (for k4 and newer)
}

#=============================
# line - Bresenham's line algorithm
# usage: line x0 y0 x y
#-----------------------------
line() {
  local x0=$(($1/$KQ)) y0=$2 x=$(($3/$KQ)) y=$4; local dx dy sx sy e e2 xw xp=$x0
  if [[ $x -gt $x0 ]];then dx=$((x-x0));sx=1; else dx=$((x0-x));sx=-1;fi
  if [[ $y -gt $y0 ]];then dy=$((y0-y));sy=1; else dy=$((y-y0));sy=-1;fi
  e=$((dx+dy)); echo -en "\xff${b#?}" | dd of=$DF bs=1 count=1 seek=$((y*VX+x)) 2>$DN
  while [[ $x0 -ne $x -o $y0 -ne $y ]];do
    xw=$((x0-xp)); [[ $xw -lt 0 ]]&& xw=$((-xw))
    echo -en "\xff${b#?}" | dd  of=$DF bs=1 count=1 seek=$((y0*VX+x0)) 2>$DN
    e2=$((e+e))
    if [[ $e2 -gt $dy ]];then e=$((e+dy));x0=$((x0+sx));fi
    if [[ $e2 -lt $dx ]];then
      xp=$x0;e=$((e+dx));y0=$((y0+sy))
    fi
  done;
}

#=============================
# sin - Taylor series sine function
# sin x ~= x - x^3/3! + x^5/5! - x^7/7!
# usage: sin x  (-PI..PI)
# STDOUT: sin(x)  (0..10K)
# O(5) @ +-90 deg, O(7) @ +-180 deg
# 10K fixed-point scale factor 
#------------------------------
sin() {
  local x=$1
  while [[ $x -gt $PI ]];do x=$((x-$P2));done
  while [[ $x -lt $PN ]];do x=$((x+$P2));done
  local a=$x xs=$(((x*x+FH)/FP))
  local xn=$(((x*xs+FH)/FP)) fn=6 # O(3)
  a=$((a-(xn+fn/2)/fn)) xn=$(((xn*xs+FH)/FP)) fn=$((fn*20)) # O(5)
  a=$((a+(xn+fn/2)/fn)) xn=$(((xn*xs+FH)/FP)) fn=$((fn*42)) # O(7)
  a=$((a-(xn+fn/2)/fn)); echo $a
}

#=============================
# cos(x) = sin(pi/2-x) function
# usage: cos x  (-PI..PI)
# STDOUT: cos(x)  (0..10K)
# 10K fixed-point scale factor 
#------------------------------
cos() {
sin $((PI/2+$(mult -10000 $1)))
}

#=============================
# mult -  multiply scaled numbers
# usage: mult x y
# STDOUT: x * y  (0..10K)
# 10K fixed-point scale factor 
#------------------------------
mult() {
echo $((($1*$2+FH)/FP))
}

#=============================
# mat3d - compute projection matrix coefficients
# usage: mat3d 
#-----------------------------
mat3d() {
  CH=$(cos $H) SH=$(sin $H)
  CP=$(cos $P) SP=$(sin $P)
  CB=$(cos $B) SB=$(sin $B)
  AM=$(( $(mult CB CH) - $(mult $(mult SH SP) SB)));	#   cos(B) cos(H) - sin(P) sin(B) sin(H)
  BM=$((-$(mult CB SH) - $(mult $(mult SP CH) SB)));	# - cos(B) sin(H) - sin(P) sin(B) cos(H)
  CM=$(( $(mult CP SB)));				#   sin(B) cos(P)
  DM=$(( $(mult CP SH)));				#   sin(H) cos(P)
  EM=$(( $(mult CP CH)));				#   cos(P) cos(H)
  FM=$SP;						#   sin(P)
  GM=$((-$(mult SB CH) - $(mult $(mult SH SP) CB)));	# - cos(H) sin(B) - sin(H) sin(P) cos(B)
  HM=$(( $(mult SB SH) - $(mult $(mult CH SP) CB)));	#   sin(H) sin(B) - sin(P) cos(H) cos(B)
  IM=$(( $(mult CP CB)));				#   cos(P) cos(B)
}

#==============================
# proj2d - project a vector
# usage: proj2d 
#------------------------------
proj2d() {
  XX=$((XX-XV))
  YY=$((YY-YV))
  ZZ=$((ZZ-ZV))
  X3=$(($(mult AM XX)+$(mult BM YY)+$(mult CM ZZ)))
  Y3=$(($(mult DM XX)+$(mult EM YY)+$(mult FM ZZ)))
  Z3=$(($(mult GM XX)+$(mult HM YY)+$(mult IM ZZ)))
  U=$((HX+ $(mult $(mult 300000 D)/Y3 X3))); # 30 horiz ppu adjustment
  V=$((HY+ $(mult $(mult 300000 D)/Y3 Z3))); # 30 vert ppu adjustment
}

#=============================
# plot 3D object on 2D screen
# usage: plot object
#-----------------------------
plot() {
  # read object vectors and edges:
  NV=$1; shift; # number of points
  for i in $(seq $NV); do
    echo $((VECTX$i=$1)) $((VECTY$i=$2)) $((VECTZ$i=$3)) > $DN
    shift; shift; shift
  done
  NE=$1; shift  # number of edges
  for i in $(seq $NE); do
    echo $((EDGE$i=$1)) > $DN; shift
  done

  while :; do

    # project object:
    eips -c
    eips  0 39 "P=$P H=$H B=$B D=$D"
    mat3d
    XV=$((-$(mult $(mult D CP) SH)))
    YV=$((-$(mult $(mult D CP) CH)))
    ZV=$((-$(mult D SP)))
    for i in $(seq $NE); do
      echo $((EDGEi=EDGE$i))>$DN
      EDGE=$EDGEi
      [[ $EDGE -le 0 ]]&& EDGE=$((-EDGE)) # ABS(EDGE)
      echo $((XX=VECTX$EDGE))>$DN; echo $((YY=VECTY$EDGE))>$DN; echo $((ZZ=VECTZ$EDGE))>$DN
      proj2d
      if [[ $EDGEi -gt 0 ]]; then
        x2=$U  y2=$V
        line $x1 $y1 $x2 $y2; eupd
      fi
      x1=$U  y1=$V
    done

    if [[ -s $TK ]];then # get new Pitch, Heading, Bank, or Distance from keyboard input  
      set $(cat $TK); local k=$1; echo > $TK; waitforkey > $TK &
      case $k in
        25)	P=$((P+P2/36));;	# p +10dg
        35)	H=$((H+P2/36));;	# h +10dg
        48)	B=$((B+P2/36));;	# b +10dg
        44)	D=$((D-2*FP));;		# z zoom in
        22)	D=$((D+2*FP));;		# u zoom out
        19)	P=0 H=0 B=0 D=135000;;	# r home
        102)    return;;		# HOME quit program
      esac
    fi 
   
  done
}

#=============================
# main program entry point
#-----------------------------                  
lipc-set-prop com.lab126.powerd preventScreenSaver 1
killall -stop cvm # pause framework
initvar # init global vars
while :; do
  eips -c 
  eips 15  4 "    _/_/_/    3D"
  eips 15  5 "         _/  4.0 _/_/_/"
  eips 15  6 "    _/_/    by  _/    _/"
  eips 15  7 "       _/  PoP _/    _/"
  eips 15  8 "_/_/_/        _/    _/"
  eips 15  9 "             _/_/_/"
  eips 15 15 "Select object to plot:"
  eips 15 16 "Q    - BLOCK"
  eips 15 17 "W    - OCTAHEDRON"
  eips 15 18 "E    - TETRAHEDRON"
  eips 15 19 "R    - DODECAHEDRON"
  eips 15 20 "Home - Quit"
  eips 15 28 "While object is being plot:"
  eips 15 29 "R      reset view"
  eips 15 30 "P      + pitch"
  eips 15 31 "H      + heading"
  eips 15 32 "B      + bank"
  eips 15 33 "Z      zoom"
  eips 15 34 "U      un zoom"
  eips 15 35 "Home - Select new object"
  waitforkey > $TK; set $(cat $TK); local k=$1;  echo > $TK;
  case $k in
    16)	plot $BLOCK;;
    17)	plot $OCTA;;
    18)	plot $TETRA;; 
    19)	plot $DODECA;;
   102) killall -cont cvm; lipc-set-prop com.lab126.powerd preventScreenSaver 0; exit;;
  esac 
done